Skip to main content

Azure Functions and .NET 5: Query params, Dependency Injection, Bicep & Build

· 4 min read
John Reilly

The upgrade of Azure Functions from .NET Core 3.1 to .NET 5 is significant. There's an excellent guide for the general steps required to perform the upgrade. However there's a number of (unrelated) items which are not covered by that post:

  • Query params
  • Dependency Injection
  • Bicep
  • Build

This post will show how to tackle these.

title image showing name of post and the Azure Functions logo

Query params

As part of the move to .NET 5 functions, we say goodbye to HttpRequest and hello to HttpRequestData. Now HttpRequest had a useful Query property which allowed for the simple extraction of query parameters like so.

var from = req.Query["from"]

HttpRequestData has no such property. However, it's straightforward to make our own. It's simply a matter of using System.Web.HttpUtility.ParseQueryString on req.Url.Query and using that:

var query = System.Web.HttpUtility.ParseQueryString(req.Url.Query);
var from = query["from"]

Dependency Injection, local development and Azure Application Settings

Dependency Injection is a much more familiar shape in .NET 5 if you're familiar with .NET Core web apps. Once again we have a Program.cs file. To get the configuration built in such a way to support both local development and when deployed to Azure, there's a few things to do. When deployed to Azure you'll likely want to read from Azure Application Settings:

screenshot of Azure Application Settings

To tackle both of these, you'll want to use AddJsonFile and AddEnvironmentVariables in ConfigureAppConfiguration. A final Program.cs might look something like this:

using System;
using System.Threading.Tasks;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace MyApp
{
public class Program
{
public static Task Main(string[] args)
{
var host = new HostBuilder()
.ConfigureAppConfiguration(configurationBuilder =>
configurationBuilder
.AddCommandLine(args)
// below is for local development
.AddJsonFile("local.settings.json", optional: true, reloadOnChange: true)
// below is what you need to read Application Settings in Azure
.AddEnvironmentVariables()
)
.ConfigureFunctionsWorkerDefaults()
.ConfigureServices(services =>
{
services.AddLogging();
services.AddHttpClient();
})
.Build();

return host.RunAsync();
}
}
}

With this approach in place, when the application runs, it should construct a configuration driven by all the providers required to run our application.

Bicep

When it comes to deploying to Azure via Bicep, there's some small tweaks required:

  • appSettings.FUNCTIONS_WORKER_RUNTIME becomes dotnet-isolated
  • linuxFxVersion becomes DOTNET-ISOLATED|5.0

Applied to the resource itself the diff looks like this:

resource functionAppName_resource 'Microsoft.Web/sites@2018-11-01' = {
name: functionAppName
location: location
tags: tags_var
kind: 'functionapp,linux'
identity: {
type: 'SystemAssigned'
}
properties: {
serverFarmId: appServicePlanName_resource.id
siteConfig: {
http20Enabled: true
remoteDebuggingEnabled: false
minTlsVersion: '1.2'
appSettings: [
{
name: 'FUNCTIONS_EXTENSION_VERSION'
value: '~3'
}
{
name: 'FUNCTIONS_WORKER_RUNTIME'
- value: 'dotnet'
+ value: 'dotnet-isolated'
}
{
name: 'AzureWebJobsStorage'
value: 'DefaultEndpointsProtocol=https;AccountName=${storageAccountName};AccountKey=${listKeys(resourceId('Microsoft.Storage/storageAccounts', storageAccountName), '2019-06-01').keys[0].value};EndpointSuffix=${environment().suffixes.storage}'
}
]
connectionStrings: [
{
name: 'TableStorageConnectionString'
connectionString: 'DefaultEndpointsProtocol=https;AccountName=${storageAccountName};AccountKey=${listKeys(resourceId('Microsoft.Storage/storageAccounts', storageAccountName), '2019-06-01').keys[0].value};EndpointSuffix=${environment().suffixes.storage}'
}
]
- linuxFxVersion: 'DOTNETCORE|LTS'
+ linuxFxVersion: 'DOTNET-ISOLATED|5.0'
ftpsState: 'Disabled'
managedServiceIdentityId: 1
}
clientAffinityEnabled: false
httpsOnly: true
}
}

Building .NET 5 functions

Before signing off, there's one more thing to slip in. When attempting to build .NET 5 Azure Functions with the .NET SDK alone, you'll encounter this error:

The framework 'Microsoft.NETCore.App', version '3.1.0' was not found.

Docs on this seem to be pretty short. The closest I came to docs was this comment on Stack Overflow:

To build .NET 5 functions, the .NET Core 3 SDK is required. So this must be installed alongside the 5.0.x sdk.

So with Azure Pipelines you might have have something that looks like this:

stages:
- stage: build
displayName: build
pool:
vmImage: 'ubuntu-latest'
jobs:
- job: BuildAndTest
displayName: 'Build and Test'
steps:
# we need .NET Core SDK 3.1 too!
- task: UseDotNet@2
displayName: 'Install .NET Core SDK 3.1'
inputs:
packageType: 'sdk'
version: 3.1.x

- task: UseDotNet@2
displayName: 'Install .NET SDK 5.0'
inputs:
packageType: 'sdk'
version: 5.0.x

- task: DotNetCoreCLI@2
displayName: 'function app test'
inputs:
command: test

- task: DotNetCoreCLI@2
displayName: 'function app build'
inputs:
command: build
arguments: '--configuration Release --output $(Build.ArtifactStagingDirectory)/MyApp'

- task: DotNetCoreCLI@2
displayName: 'function app publish'
inputs:
command: publish
arguments: '--configuration Release --output $(Build.ArtifactStagingDirectory)/MyApp /p:SourceRevisionId=$(Build.SourceVersion)'
publishWebProjects: false
modifyOutputPath: false
zipAfterPublish: true

- publish: $(Build.ArtifactStagingDirectory)/MyApp
artifact: functionapp

Have fun building .NET 5 functions!