Azure Functions v3 migration guide from .NET Core 3.1 to Isolated Model on .NET 5
This guide will help you migrate from a project with Azure Functions v3 on .NET Core 3.1 to the new Azure Functions Isolated Model on .NET 5.
⚠ ⚠ ⚠ This guide doesn’t provide an exhaustive list of steps. It is based on my own migration and cover only a very small subset of all the possibilities offered by the Azure Functions. Some information may not be accurate anymore. ⚠ ⚠ ⚠
Step 0 — Environment checks
The very first step is to install the .NET 5 SDK.
The second important step is to ensure that the Azure Functions Core Tools version is at greater than 3.0.3477 (func --help
).
Then you should edit your global.json
file, if any. To get which version to use, you can run the command-line dotnet --info
and pick the latest version of .NET 5 SDK installed.
from
{
"sdk": {
"version": "3.1.404",
"rollForward": "latestFeature"
}
}
to
{
"sdk": {
"version": "5.0.301",
"rollForward": "latestFeature"
}
}
Step 1 — Update the CSPROJ
Update the CSPROJ file:
- to target the .NET 5 framework
- switch the assembly output to “Exe”
- use the new NuGet packages
from
<TargetFramework>netcoreapp3.1</TargetFramework>
to
<TargetFramework>net5.0</TargetFramework>
<OutputType>Exe</OutputType>
If you encounter this following error, you need either to add the SelfContained
property or the OutputType
property (as shown in the previous snippet).
1>C:\Users\adrien\.nuget\packages\microsoft.azure.functions.worker.sdk\1.0.1\build\Microsoft.Azure.Functions.Worker.Sdk.targets(51,9): error MSB4113: Specified condition “$(SelfContained)” evaluates to “” instead of a boolean.
Then we update the NuGet packages:
from
<PackageReference Include="Microsoft.NET.Sdk.Functions" Version="3.0.11" />
to
<PackageReference Include="Microsoft.Azure.Functions.Worker.Sdk" Version="1.0.3" OutputItemType="Analyzer" /><PackageReference Include="Microsoft.Azure.Functions.Worker" Version="1.3.0" />
It’s also a good time to update other Microsoft.* packages to use the .NET 5 version.
Step 2 — Add a new entry point
Like any other ASP.NET core app project, we need to provide an entry point to define and start a host:
At this point, you should be able to run the project. But no function will be available.
Step 3 — Update the settings files
Update within local.settings.json
the FUNCTION_WORKER_RUNTIME
from dotnet
to dotnet-isolated
.
Step 4 — Fix namespace
Use the new namespace from the Azure Function Worker:
from
using Microsoft.Azure.WebJobs;
to
using Microsoft.Azure.Functions.Worker;
Step 5 — Fix FunctionName attribute
Rename [FunctionName("xxx")]
attribute on each function to [Function("xxx")]
.
The attribute is provided by the Microsoft.Azure.Functions.Worker
.
Step 6 — Add extensions NuGet packages related to your usage
For HttpTrigger
, add Microsoft.Azure.Functions.Worker.Extensions.Http.
For ServiceBugTrigger
, add Microsoft.Azure.Functions.Worker.Extensions.ServiceBus.
Convert all namespaces containing WebJobs
their Worker
equivalent.
For HttpTrigger:
using Microsoft.Azure.WebJobs.Extensions.Http;
to
using Microsoft.Azure.Functions.Worker.Http;
Step 7 — Remove additional *.WebJobs.* packages and namespaces
Some packages use WebJobs packages internally and should be removed. For example Microsoft.Azure.Functions.Extensions contains FunctionInvocationException
(if you catch globally all exceptions).
Some WebJobs packages have helpers around IConfiguration
and IConfigurationRoot
. So if you don’t have access to some methods you probably need to add Microsoft.Extensions.Configuration.Binder or Microsoft.Extensions.Configuration.Abstractions packages.
Step 8 — Convert ExecutionContext
The classExecutionContext
has been renamed to FunctionContext
.
context.FunctionName
property is now context.FunctionDefinition.Name
.
If you used ExecutionContext
in a class library targeting netstandard2.0
you would need to switch it to .NET 5 as the Worker and Worker.Core target both .NET 5.
Step 9/A — HTTP
New package:
Replace HttpRequestMessage
and HttpResponseMessage
by HttpRequestData
and HttpResponseData
:
from
public async Task<HttpResponseMessage> RunAsync([HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = FunctionRoute)] HttpRequestMessage req)
{
}
to
public async Task<HttpResponseData> RunAsync([HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = FunctionRoute)] HttpRequestData req)
{
}
HttpRequestData
is now in a package targeting .NET 5, so be careful if you used HttpRequestMessage
in a library targeting netstandard2.0
.
Request URI:
req.RequestUri
is now req.Url
.
Returning result (as JSON) with HTTP status code:
There are extensions methods provided by HttpResponseDataExtensions
to build the response from the request.
In Azure Function on .NET Core 3.1, this following code snippet that returns a JSON object serialized as a StringContent within the response:
var response = new HttpResponseData(httpCode);response.RequestMessage = request;response.Content = new StringContent(json, Encoding.UTF8, "application/json");return response;
need to be converted to:
var response = request.CreateResponse(httpCode);response.Headers.Add("Content-Type", "application/json");await response.WriteStringAsync(json, Encoding.UTF8);return response;
Or you can use directly the WriteAsJsonAsync
extension methods. The WriteAsJsonAsync
add the application/json
header. So don’t add it again or you will get an error.
⚠ ⚠ ⚠ In the current release, be careful about WriteAsJsonAsync
, this method updates the HTTP status code to 200 OK. If you use another status code within the constructor, it will be overwritten (see GitHub issue). ⚠ ⚠ ⚠
Serialization:
The serializer can be configured within the WorkerOptions
. Internally the method WriteAsJsonAsync
configure the serializer as following:
public static ValueTask WriteAsJsonAsync<T>(this HttpResponseData response, T instance, string contentType, CancellationToken cancellationToken = default(CancellationToken))
{
...ObjectSerializer serializer = response.FunctionContext.InstanceServices.GetService<IOptions<WorkerOptions>>()?.Value?.Serializer ?? throw new InvalidOperationException("A serializer is not configured for the worker.");return response.WriteAsJsonAsync(instance, serializer, contentType, cancellationToken);
}
Route data:
Extracting route data (MS_AzureWebJobs_HttpRouteData
) is a easier now; instead of req.Properties.TryGetValue(“HttpContext”, out var httpContext)
we can simply retrieve the information from req.FunctionContext.BindingContext.BindingData
.
Let’s define an HttpTrigger
function with the route service/{routeParam1}/test/{routeParam2}/end-call.
If you try calling function with some query parameters and a JSON:
http://localhost:7071/api/service/valueParam1/test/valueParam2/end-call?queryParam1=abcd&queryParam2=1234
with body
{
"bodyValue1": "xxxx",
"bodyValue2": true,
"bodyValue3": 3,
"bodyValue4": {
"innerBodyValye4": "yyyy"
}
you will get a dictionary containing:
To extract only the route parameters, we can easily match which parameter belongs to the route by parsing the function route.
Identity:
The ClaimsPrincipal
passed as a parameter of the Function can be removed and replaced by the req.Identities
(from HttpRequestData
).
Useful links:
- GrpcHttpRequestData
- Is it possible to register request-scoped dependencies? · Issue #436 · Azure/azure-functions-dotnet-worker (github.com)
Step 9/B — ServiceBus
New package:
The only possible way, for the moment (at least until .NET 6.0 InProc version), to read the service bus message is to get the binding data data
. You get the message content as a string
by adding a parameter to the method.
[Function("FunctionName")]
public static async Task Run(
[ServiceBusTrigger(
queueName: "queueNameInAzure",
Connection = "my-localsetting-connection"
)] string data,
FunctionContext functionContext,
string contentType,
string lockToken)
{
}
MessageReceiver
cannot be used for the moment. If we try to Complete
a message we have the usual error:
The lock supplied is invalid. Either the lock expired, or the message has already been removed from the queue, or was received by a different receiver instance.
FunctionContext/BindingData:
It seems that the properties of the message are splitted inside the BindingData
so, in the case of the CloudEvent formatted message I have sent over the bus, we can access directly “root” properties with functionContext.BindingContext.BindingData
:
Settings:
Unfortunately, setting binding using the Azure App Configuration is not working for the moment. So you cannot write:
[ServiceBusTrigger(
topicName: "%bus-sample/my-topic%",
subscriptionName: "%bus-sample/my-subscription%",
Connection = "%bus-sample/connection%"
)]
Message autocompletion:
⚠ ⚠ ⚠ This doesn’t work for the moment. ⚠ ⚠ ⚠
But we still need to “fix” the code to make it compiles correctly. So we can either remove it or change it and fix it later when this feature will be fully available.
If you autocomplete messages, you probably have the following code or similar in the startup:
builder.Services.Configure<ServiceBusOptions>(options =>
{
options.MessageHandlerOptions.AutoComplete = false;
});
The ServiceBusOptions
is not available anymore; so we need to add the Azure.Messaging.ServiceBus package:
And change the configuration to:
services.Configure<ServiceBusProcessorOptions>(options =>
{
options.AutoCompleteMessages = false;
});
An additional and far more complete migration guide is provided at: azure-sdk-for-net/MigrationGuide.md at master · Azure/azure-sdk-for-net (github.com).
Step 9/C — EventHub
New package:
The old package has a reference to Microsoft.Azure.EventHubs:
To get back the EventData
class, you need to install the new package Azure.Messaging.EventHubs:
and update the namespace from
using Microsoft.Azure.EventHubs;
to
using Azure.Messaging.EventHubs;
The eventData.Body
is now eventData.EventBody
.
Step 10 — [NotAutomaticTrigger] attribute
This attribute doesn’t exist anymore. To ease the migration I would suggest creating your own for the moment. You can remove it later as it is now useless.
using System;namespace Microsoft.Azure.Functions.Worker
{
public class NoAutomaticTriggerAttribute : Attribute
{
}
}
Step 11 — Convert your Startup.cs class
If you used a custom Startup
class to configure your services for dependency injection; you probably need to split it into the new IHostBuilder
methods:
- ConfigureLogging()
- ConfigureAppConfiguration()
- ConfigureServices()
These methods can be called multiple times at different places; this is pretty convenient to split configuration files neatly.
If you have some extensions methods in netstandard2.0 libraries, you will probably need to target net5.0 because of the IHostBuilder
. This interface comes with the Microsoft.Azure.Functions.Worker.Core package.
The startup class need to be converted:
from
to
We can then update the HostBuilder
inProgram.cs
and call the new ConfigureCustomFunctionsWorker()
method:
Step 12 — ApplicationInsights
First we need to use another NuGet packages:
from
<PackageReference
Include=”Microsoft.Azure.WebJobs.Logging.ApplicationInsights”
Version=”3.0.23" />
to
<PackageReference
Include=”Microsoft.ApplicationInsights.WorkerService”
Version=”2.17.0" />
And then we need to update the services registration:
from
services.AddApplicationInsightsTelemetry();
to
services.AddApplicationInsightsTelemetryWorkerService();
Step 13 — Feature Flags
Replace IConfigurationRefresher
by IConfigurationRefresherProvider
.
Additional Step — Convert old code to .NET 5
Some parts of the code have been moved to their specific NuGet packages and need to be migrated to their .NET 5 equivalent.
Some examples are
Additional Links
.NET isolated process guide for .NET 5.0 in Azure Functions | Microsoft Docs