DEV Community

epifive
epifive

Posted on Originally published at epifive.com

Migrate Azure Functions from In-Process to the Isolated Worker Model

Originally published on EpiFive, where it is kept up to date.

Support for the Azure Functions in-process model ends on November 10, 2026. To keep a C# function app supported, convert it to the isolated worker model: replace the project SDK and packages, add a Program.cs that builds the host, update function signatures and bindings, then change FUNCTIONS_WORKER_RUNTIME to dotnet-isolated and publish the new code together through a staging slot swap.

This guide follows Microsoft's migration article and adds the checks and failure modes you are likely to hit along the way. It was checked against the Microsoft Learn pages listed in the References section on 2026-10-27.

Who this is for and what you will have at the end

This article is for developers and IT administrators who own one or more .NET function apps that use the in-process model, meaning a class library with the FunctionName attribute and the Microsoft.NET.Sdk.Functions package. You do not need to know the isolated worker model already, but you should be able to build and run a function app locally.

By the end you will have a list of the in-process apps in your subscription, a converted project that builds and runs under Azure Functions Core Tools, and a deployment procedure that avoids a production outage between the runtime setting change and the code deployment.

The model differences matter because the isolated worker runs your code in a separate process from the Functions host. You control start-up, dependency injection and middleware yourself, in the same style as an ASP.NET Core app.

Prerequisites and decisions

Before you touch code, settle the following.

Item What to check
Functions runtime version The guide assumes version 4.x. Apps on 1.x, 2.x or 3.x use the version 4.x migration guides first.
Target framework Target .NET 10 if your app and its dependencies run on modern .NET. Target .NET Framework 4.8 only if you depend on libraries that exist only there.
Hosting plan .NET 10 apps cannot run on Linux in the Consumption plan. On Linux, use the Flex Consumption plan.
Deployment slot Slots are not currently supported on Flex Consumption. Test in a non-production app and see the site update strategies page for rolling updates.
Tooling Install the .NET SDK for your target version and Azure Functions Core Tools 4.x.

Check the runtime version of each app. In the Azure portal, open the function app, then Settings > Configuration, and look at the Runtime version value on the Function runtime settings tab. With the Azure CLI, list the app settings and read FUNCTIONS_EXTENSION_VERSION:

az functionapp config appsettings list --name "<FUNCTION_APP_NAME>" --resource-group "<RESOURCE_GROUP_NAME>"
Enter fullscreen mode Exit fullscreen mode

A value of ~4 means the app is already on version 4.x. If it is not, follow the dedicated version migration guides instead of only changing that setting.

Step 1: Find the apps that need migrating

Microsoft's guide provides an Azure PowerShell script that lists function apps in the current subscription whose runtime is dotnet. That value identifies in-process apps. The script uses whichever subscription Azure PowerShell is currently set to, and you change it with Set-AzContext.

Set-AzContext -Subscription '<YOUR SUBSCRIPTION ID>'

$FunctionApps = Get-AzFunctionApp

$AppInfo = @{}

foreach ($App in $FunctionApps)
{
     if ($App.Runtime -eq 'dotnet')
     {
          $AppInfo.Add($App.Name, $App.Runtime)
     }
}

$AppInfo
Enter fullscreen mode Exit fullscreen mode

Run it once per subscription. Record each app name, its plan type and which repository holds its code. Apps with no known owner or repository are the biggest risk, because you cannot rebuild them. If you manage subscriptions through a landing zone design, the subscription layout described in A practical Azure landing zone for small and mid-size companies helps you decide where to run the script.

Step 2: Convert the project file

Start with the project file, then follow the compiler errors. The migration guide recommends doing the .csproj and package changes first because the resulting build errors point at every remaining change.

A typical in-process project on .NET 8 looks like this:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
    <AzureFunctionsVersion>v4</AzureFunctionsVersion>
    <RootNamespace>My.Namespace</RootNamespace>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Microsoft.NET.Sdk.Functions" Version="4.1.1" />
  </ItemGroup>
  <ItemGroup>
    <None Update="host.json">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="local.settings.json">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
      <CopyToPublishDirectory>Never</CopyToPublishDirectory>
    </None>
  </ItemGroup>
</Project>
Enter fullscreen mode Exit fullscreen mode

For .NET 10, make these changes:

  1. Set the Sdk attribute on the Project element to Azure.Functions.Sdk/1.0.0.
  2. Set TargetFramework to net10.0.
  3. Remove the AzureFunctionsVersion property. The new SDK defaults to Functions runtime version 4.x.
  4. Replace the Microsoft.NET.Sdk.Functions package reference with the worker packages.
  5. Add an ItemGroup with a Using alias for System.Threading.ExecutionContext.

The result from Microsoft's guide:

<Project Sdk="Azure.Functions.Sdk/1.0.0">
  <PropertyGroup>
    <TargetFramework>net10.0</TargetFramework>
    <RootNamespace>My.Namespace</RootNamespace>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>
  <ItemGroup>
    <FrameworkReference Include="Microsoft.AspNetCore.App" />
    <PackageReference Include="Microsoft.Azure.Functions.Worker" Version="2.52.0" />
    <PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore" Version="2.1.0" />
    <PackageReference Include="Microsoft.ApplicationInsights.WorkerService" Version="2.22.0" />
    <PackageReference Include="Microsoft.Azure.Functions.Worker.ApplicationInsights" Version="1.2.0" />
    <!-- Other packages may also be in this list -->
  </ItemGroup>
  <ItemGroup>
    <None Update="host.json">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="local.settings.json">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
      <CopyToPublishDirectory>Never</CopyToPublishDirectory>
    </None>
  </ItemGroup>
  <ItemGroup>
    <Using Include="System.Threading.ExecutionContext" Alias="ExecutionContext"/>
  </ItemGroup>
</Project>
Enter fullscreen mode Exit fullscreen mode

The version numbers above are the ones printed in the guide on the date checked. Use the latest stable versions of Microsoft.Azure.Functions.Worker and Azure.Functions.Sdk when you do the work. For .NET Framework 4.8, the guide uses net48, the Microsoft.Azure.Functions.Worker.Extensions.Http package instead of the ASP.NET Core one, and ConfigureFunctionsWorkerDefaults in Program.cs.

If you target .NET 10 and want the tool to do part of the work, the guide notes that the .NET Upgrade Assistant can make many of these changes automatically.

Also check anything outside the project that mentions the framework version, such as build steps, CI/CD pipelines and, in Visual Studio Code, the azureFunctions.deploySubpath setting.

Step 3: Replace the binding packages

Your isolated project must not reference any package in the Microsoft.Azure.WebJobs.* namespaces or Microsoft.Azure.Functions.Extensions. Replace each with its worker equivalent. Some of the common ones from the guide:

In-process reference Isolated replacement
Microsoft.Azure.WebJobs.Extensions.Storage.Blobs Microsoft.Azure.Functions.Worker.Extensions.Storage.Blobs
Microsoft.Azure.WebJobs.Extensions.Storage.Queues Microsoft.Azure.Functions.Worker.Extensions.Storage.Queues
Microsoft.Azure.WebJobs.Extensions.Tables Microsoft.Azure.Functions.Worker.Extensions.Tables
Microsoft.Azure.WebJobs.Extensions.ServiceBus Microsoft.Azure.Functions.Worker.Extensions.ServiceBus
Microsoft.Azure.WebJobs.Extensions.EventHubs Microsoft.Azure.Functions.Worker.Extensions.EventHubs
Microsoft.Azure.WebJobs.Extensions.CosmosDB Microsoft.Azure.Functions.Worker.Extensions.CosmosDB
Microsoft.Azure.WebJobs.Extensions.DurableTask Microsoft.Azure.Functions.Worker.Extensions.DurableTask
Microsoft.Azure.Functions.Extensions Remove it. Dependency injection and start-up configuration are built in.

The timer trigger is a special case: add Microsoft.Azure.Functions.Worker.Extensions.Timer. Install the latest stable version of each extension and read its isolated-process documentation, because a new extension version can require host.json changes. The Service Bus extension, for example, changed its host.json structure between versions 4.x and 5.x.

Step 4: Add Program.cs and remove Startup

The isolated worker needs an entry point. Add a Program.cs like this for .NET 10 with ASP.NET Core integration:

using Microsoft.Azure.Functions.Worker;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

var host = new HostBuilder()
    .ConfigureFunctionsWebApplication()
    .ConfigureServices(services => {
        services.AddApplicationInsightsTelemetryWorkerService();
        services.ConfigureFunctionsApplicationInsights();
    })
    .Build();

host.Run();
Enter fullscreen mode Exit fullscreen mode

ConfigureFunctionsWebApplication enables ASP.NET Core integration, which gives HTTP triggers the familiar HttpRequest and IActionResult types. If you have no HTTP triggers you can call ConfigureFunctionsWorkerDefaults instead and drop the Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore reference. The guide still recommends keeping the ASP.NET Core FrameworkReference for best performance.

Program.cs replaces any file carrying the FunctionsStartup attribute, usually Startup.cs. Move each builder.Services registration from that class into the ConfigureServices delegate, then delete the attribute and the class.

Be aware of two behaviours:

  • host.json only controls events emitted by the Functions host. Logs from your own code are filtered by rules you configure in Program.cs. If you skip this, the log levels in Application Insights can differ from what you had before.
  • Custom configuration sources registered on the HostBuilder apply only to code in your project. Trigger and binding configuration must still come from application settings, Key Vault references or App Configuration references.

The guide's default sample uses the Application Insights SDK. The isolated worker guide also describes an OpenTelemetry-based setup that calls AddOpenTelemetry().UseFunctionsWorkerDefaults() and AddOpenTelemetry().UseAzureMonitorExporter(). Pick one approach per app rather than mixing them.

Step 5: Update function signatures and bindings

Work through each function. The changes are mostly mechanical.

Attribute and logger

Replace FunctionName with Function. It has the same signature, so a find-and-replace works.

Replace the ILogger method parameter with constructor injection of ILogger<T>:

public class MyFunction
{
    private readonly ILogger<MyFunction> _logger;

    public MyFunction(ILogger<MyFunction> logger)
    {
        _logger = logger;
    }
}
Enter fullscreen mode Exit fullscreen mode

Then change calls from the old log parameter to _logger and remove the parameter from the signature.

Trigger and binding attributes

Remove using Microsoft.Azure.WebJobs; and add using Microsoft.Azure.Functions.Worker;. Then follow these naming rules from the guide:

  • Triggers usually keep their name. QueueTrigger is the same in both models.
  • Input bindings usually gain Input. The CosmosDB input attribute becomes CosmosDBInput.
  • Output bindings usually gain Output. The Queue output attribute becomes QueueOutput.

Parameters also change. The in-process blob output [Blob("sample-images-sm/{fileName}", FileAccess.Write, Connection = "MyStorageConnection")] becomes [BlobOutput("sample-images-sm/{fileName}", Connection = "MyStorageConnection")], because the Access property is no longer needed.

Output bindings move out of the parameter list. With one output, apply the attribute to the return type. With several, create a class with one property per output and put the attributes on those properties. If you used IAsyncCollector<T>, bind to T[] instead. If you used IBinder, remove it and inject the Azure service client you need.

A before-and-after HTTP trigger

In-process:

using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.Extensions.Logging;

namespace Company.Function
{
    public static class HttpTriggerCSharp
    {
        [FunctionName("HttpTriggerCSharp")]
        public static IActionResult Run(
            [HttpTrigger(AuthorizationLevel.Function, "get", Route = null)] HttpRequest req,
            ILogger log)
        {
            log.LogInformation("C# HTTP trigger function processed a request.");

            return new OkObjectResult($"Welcome to Azure Functions, {req.Query["name"]}!");
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Isolated worker on .NET 10:

using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Extensions.Logging;

namespace Company.Function
{
    public class HttpTriggerCSharp(ILogger<HttpTriggerCSharp> logger)
    {
        [Function("HttpTriggerCSharp")]
        public IActionResult Run(
            [HttpTrigger(AuthorizationLevel.Function, "get")] HttpRequest req)
        {
            logger.LogInformation("C# HTTP trigger function processed a request.");

            return new OkObjectResult($"Welcome to Azure Functions, {req.Query["name"]}!");
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

The class is no longer static, because it receives its logger through the constructor.

Serialization and stream changes

Two behaviour changes do not produce compiler errors, so look for them on purpose.

First, the isolated worker uses System.Text.Json by default, while the in-process model used Newtonsoft.Json. Attributes such as [JsonProperty] and [JsonIgnore] from Newtonsoft are ignored, and the affected property binds to its default value without any error. Replace them with System.Text.Json equivalents such as [JsonPropertyName], or configure Newtonsoft for the layer that handles the payload.

Second, with ASP.NET Core integration, synchronous reads and writes on request and response streams can fail with InvalidOperationException: Synchronous operations are disallowed. Use ReadToEndAsync, WriteAsync, WriteStringAsync and FlushAsync, and make the method async with a Task<T> return type.

Step 6: Fix local.settings.json and test locally

Change FUNCTIONS_WORKER_RUNTIME in local.settings.json to dotnet-isolated. Your AzureWebJobsStorage value does not need to change.

{
    "IsEncrypted": false,
    "Values": {
        "AzureWebJobsStorage": "UseDevelopmentStorage=true",
        "FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated"
    }
}
Enter fullscreen mode Exit fullscreen mode

Build the project, then start it with Core Tools 4.x and trigger every function at least once. Pay particular attention to functions with output bindings, JSON payloads and timer schedules, since those are the areas where behaviour changes without a build error.

dotnet build
func start
Enter fullscreen mode Exit fullscreen mode

Step 7: Cut over in Azure with a staging slot

Two changes must happen together: set FUNCTIONS_WORKER_RUNTIME to dotnet-isolated, and publish the migrated project. Each change restarts the app. Between them the configured runtime and the deployed code do not match, which causes the runtime/payload mismatch event AZFD0013. A staging slot keeps that temporary state out of production.

  1. Create a deployment slot if the app does not already have one.
  2. On the staging slot, set FUNCTIONS_WORKER_RUNTIME to dotnet-isolated. Do not mark this setting as a slot setting.
  3. If the target .NET version changes, update the stack configuration on the slot too. In the portal, go to the function app, then Settings > Configuration, select the slot, and change .NET Version on the General settings tab. For Windows apps in the CLI, use az functionapp config set --net-framework-version "v<VERSION>.0", and for Linux use --linux-fx-version with a value taken from az functionapp list-runtimes.
  4. Publish the migrated project to the staging slot.
  5. Confirm the app works in the slot and that the errors from the interim state have stopped.
  6. Swap the slot into production. A swap applies the changes as a single update.
  7. Verify production.

For a Linux app, list the valid stack values first:

az functionapp list-runtimes --os linux --query "[?runtime == 'dotnet-isolated'].{Version:version, linuxFxVersion:linux_fx_version}" --output table

az functionapp config set --linux-fx-version "<LANGUAGE|VERSION>" --name "<FUNCTION_APP_NAME>" --resource-group "<RESOURCE_GROUP_NAME>" --slot "staging"
Enter fullscreen mode Exit fullscreen mode

If you provision the app with infrastructure as code or a pipeline, update those definitions so they keep FUNCTIONS_WORKER_RUNTIME at dotnet-isolated and the right .NET version. Otherwise the next deployment can undo the change. If your pipeline deploys from GitHub Actions, workload identity federation for GitHub Actions and Azure removes stored secrets from that deployment path.

Updating the stack version restarts the app, and Microsoft's page says the app is unavailable for a brief period, typically 30 to 60 seconds, when you do it directly on production.

Verification

After the swap, check these items:

  • The app status on Overview is Running.
  • az functionapp show --name "<FUNCTION_APP_NAME>" --resource-group "<RESOURCE_GROUP_NAME>" --query "siteConfig.netFrameworkVersion" --output tsv returns the .NET version you expect.
  • FUNCTIONS_WORKER_RUNTIME reads dotnet-isolated in the app settings of the production slot.
  • Each trigger fires, and queues, topics or tables show messages being consumed.
  • Application Insights shows traces and requests, with log levels you recognise.
  • Re-run the PowerShell script from step 1. The app should no longer appear in the dotnet list.

Troubleshooting

Symptom Likely cause Fix
AZFD0013 in logs The setting and the deployed payload do not match Align FUNCTIONS_WORKER_RUNTIME with the payload. For this migration, set dotnet-isolated and deploy the isolated project, using a slot.
InvalidOperationException: Synchronous operations are disallowed Synchronous stream I/O under ASP.NET Core integration Switch to the asynchronous stream methods and make the function async.
Properties arrive as default values Newtonsoft attributes ignored by System.Text.Json Use [JsonPropertyName] or configure Newtonsoft as the serializer.
Function app does not start Dependencies or extension versions incompatible with the target framework Check log stream, update the NuGet packages and extension references, and check host.json for extension changes.
Application Insights log volume or levels changed Filtering rules live in Program.cs, not host.json Add log filtering in Program.cs for your own code.
.NET 10 app will not run on Linux Consumption Not supported on that plan Move the app to Flex Consumption.

Roll back by swapping the slot back to production, then correct the staging slot before trying again.

Closing checklist

  • List every in-process app with the Get-AzFunctionApp script.
  • Confirm each app is on runtime version 4.x.
  • Choose .NET 10 or .NET Framework 4.8 as the target.
  • Switch the project SDK to Azure.Functions.Sdk/1.0.0 and replace the WebJobs packages.
  • Add Program.cs, then delete the FunctionsStartup class.
  • Update Function attributes, loggers, bindings and output return types.
  • Review serializer attributes and synchronous stream calls.
  • Set FUNCTIONS_WORKER_RUNTIME to dotnet-isolated locally, then test every function.
  • Deploy to a staging slot, verify, and swap.
  • Update any infrastructure as code or pipeline definitions.
  • Cost review comes next: once apps are on a current stack, the Azure cost optimisation checklist is a good follow-up.

References


This guide was researched and drafted with AI assistance and checked against the vendor documentation listed in its References. Read the maintained version and related guides on EpiFive.

Top comments (0)