LowCodeStacks™
Sign in

Azure Functions .NET in-process model ends on 10 November 2026: the migration checklist

Support for the Azure Functions .NET in-process model ends on 10 November 2026. This migration checklist finds the apps that still use it, converts the project, packages, Program.cs, bindings and logging to the isolated worker model, and deploys through a staging slot without an outage.

Apps & integrationFix a problem8 min read

Checked against Microsoft Learn · 5 sourcesPosted by the Maker DeskHow we write guides

On 10 November 2026, Microsoft's support for the .NET in-process model in Azure Functions ends. Apps that still use it keep running after that date, but they stop getting security updates and bug fixes, and Microsoft's support may ask you to reproduce a problem on the supported model before helping. The replacement, the isolated worker model, runs your code in its own process, works with every current .NET version, and is the only .NET model the Flex Consumption plan accepts. The migration is mostly mechanical. This checklist walks through it in the order that produces the fewest surprises.

What changes, in one table

In-process modelIsolated worker model
Where your code runsInside the Functions host processIn a separate .NET worker process
.NET versionsLTS only, ending with .NET 8.NET 8, 9, 10 and .NET Framework 4.8
Project SDKMicrosoft.NET.Sdk.FunctionsAzure.Functions.Sdk plus Microsoft.Azure.Functions.Worker
Binding packagesMicrosoft.Azure.WebJobs.Extensions.*Microsoft.Azure.Functions.Worker.Extensions.*
Start-upA FunctionsStartup classProgram.cs with a HostBuilder
Output bindingsReturn value, out parameters, IAsyncCollectorReturn value; several outputs through a class with attributed properties
Imperative bindings (IBinder)SupportedNot supported; use the service SDK directly
MiddlewareNoYes
JSON by defaultNewtonsoft.JsonSystem.Text.Json
Flex Consumption planNot supportedSupported
SupportEnds 10 November 2026Current

The FUNCTIONS_WORKER_RUNTIME setting tells them apart: dotnet is in-process, dotnet-isolated is the worker model.

Find the apps that still run in-process

Run this in Azure PowerShell against each subscription (Set-AzContext -Subscription '<id>' to switch). Every app it lists has the dotnet runtime and needs migrating; apps already on dotnet-isolated don't appear.

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

Two checks before you touch code:

  • The Functions runtime must be 4.x. Apps on 1.x, 2.x or 3.x go through Microsoft's host-version migration guides first; those guides move you to the isolated model as you go.
  • Pick the target .NET. If the app and its libraries run on modern .NET, target .NET 10. If something depends on .NET Framework-only APIs, target .NET Framework 4.8, which the isolated model supports and the in-process model never did. C# script apps (.csx) must become a project first. Moving to .NET 10 also lets the .NET Upgrade Assistant make many of the edits below for you.

Convert the project

1. The project file

Replace the SDK and the framework, swap the core packages, and add the ExecutionContext alias (an ASP.NET Core type would otherwise shadow the Functions one):

xml
<Project Sdk="Azure.Functions.Sdk/1.0.0">
  <PropertyGroup>
    <TargetFramework>net10.0</TargetFramework>
    <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" />
  </ItemGroup>
  <ItemGroup>
    <Using Include="System.Threading.ExecutionContext" Alias="ExecutionContext" />
  </ItemGroup>
</Project>

For .NET Framework 4.8 the target is net48, the HTTP package is Microsoft.Azure.Functions.Worker.Extensions.Http (ASP.NET Core types aren't available there), and a <Folder Include="Properties\" /> item replaces the alias. Build errors appear at this point; the next steps clear them.

2. Every binding package

Each Microsoft.Azure.WebJobs.Extensions.* package has a Microsoft.Azure.Functions.Worker.Extensions.* twin. The ones most apps hit:

You hadYou need
Microsoft.NET.Sdk.FunctionsGone; the SDK and worker packages above replace it
A timer trigger (no package before)…Worker.Extensions.Timer
…WebJobs.Extensions.Storage…Worker.Extensions.Storage.Blobs, .Storage.Queues and .Tables, as needed
…WebJobs.Extensions.CosmosDB or .DocumentDB…Worker.Extensions.CosmosDB
…WebJobs.Extensions.ServiceBus…Worker.Extensions.ServiceBus (host.json settings changed between 4.x and 5.x)
…WebJobs.Extensions.EventHubs, .EventGrid, .SignalRService, .SendGrid, .Kafka, .RabbitMQThe same name under …Worker.Extensions.
…WebJobs.Extensions.DurableTask…Worker.Extensions.DurableTask (and .SqlServer or .Netherite for those storage providers)
Microsoft.Azure.Functions.Extensions (dependency injection)Remove it; the worker has dependency injection built in

When you're done, nothing in the project should reference Microsoft.Azure.WebJobs.* or Microsoft.Azure.Functions.Extensions. Take the chance to move any old Azure SDK packages to their Azure.* versions too, which the new extensions expect.

3. Program.cs replaces Startup.cs

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

var host = new HostBuilder()
    .ConfigureFunctionsWebApplication()
    .ConfigureServices(services =>
    {
        services.AddApplicationInsightsTelemetryWorkerService();
        services.ConfigureFunctionsApplicationInsights();
        // Everything that was in FunctionsStartup.Configure goes here.
    })
    .Build();

host.Run();

ConfigureFunctionsWebApplication() turns on ASP.NET Core integration, which gives HTTP triggers the familiar HttpRequest and IActionResult types and better performance. If you have no HTTP triggers you can use ConfigureFunctionsWorkerDefaults() instead, but keep the ASP.NET Core framework reference either way; Microsoft recommends it for performance. On .NET Framework use ConfigureFunctionsWorkerDefaults() and the HttpRequestData/HttpResponseData types. Delete the FunctionsStartup class once its registrations have moved.

4. Every function signature

For each function:

  1. [FunctionName("X")] becomes [Function("X")]. Same signature; a find-and-replace does it.
  2. Logging. The ILogger log parameter is gone. Inject ILogger<MyFunction> through the constructor and use the field instead.
  3. Usings. Remove using Microsoft.Azure.WebJobs;; add using Microsoft.Azure.Functions.Worker;.
  4. Attribute names. Triggers keep their names (QueueTrigger, HttpTrigger). Input bindings gain Input (CosmosDB → CosmosDBInput); output bindings gain Output (Queue → QueueOutput, Blob → BlobOutput). Some parameters go, for example the FileAccess argument on a blob output.
  5. Outputs leave the parameter list. One output: put the attribute on the return type. Several: return a class whose properties carry the attributes. IAsyncCollector<T> becomes an array, T[], or a client object you inject.
  6. IBinder goes. Replace it with the service's SDK client, injected through Program.cs.
  7. HTTP streams become asynchronous. With ASP.NET Core integration, ReadToEnd, Write and Flush on the request and response throw InvalidOperationException: Synchronous operations are disallowed. Use the Async versions and return Task<IActionResult>.

Before and after, for the simplest HTTP function:

csharp
// In-process
[FunctionName("Hello")]
public static IActionResult Run(
    [HttpTrigger(AuthorizationLevel.Function, "get")] HttpRequest req, ILogger log)
{
    log.LogInformation("Request received.");
    return new OkObjectResult($"Hello, {req.Query["name"]}!");
}

// Isolated worker, .NET 10
public class Hello(ILogger<Hello> logger)
{
    [Function("Hello")]
    public IActionResult Run([HttpTrigger(AuthorizationLevel.Function, "get")] HttpRequest req)
    {
        logger.LogInformation("Request received.");
        return new OkObjectResult($"Hello, {req.Query["name"]}!");
    }
}

5. Local settings

In local.settings.json, FUNCTIONS_WORKER_RUNTIME becomes dotnet-isolated. host.json needs no change, but read the next section about what it no longer controls.

The behaviour changes that bite

  • JSON. The worker uses System.Text.Json. It silently ignores Newtonsoft attributes such as [JsonProperty] and [JsonIgnore], binding the property to its default value with no error. Replace them with [JsonPropertyName] and friends, or configure Newtonsoft as the worker's serialiser in Program.cs through WorkerOptions.Serializer.
  • Log levels. host.json now filters only the host's own logs. Filtering for logs from your code belongs in Program.cs; without it, categories can appear at different levels in Application Insights than before.
  • Configuration sources registered in HostBuilder apply to your code only. Trigger and binding settings still have to come from application settings, Key Vault references or App Configuration references.
  • Durable Functions has its own mapping: IDurableOrchestrationContext → TaskOrchestrationContext (with context.CreateReplaySafeLogger()), IDurableOrchestrationClient → DurableTaskClient, StartNewAsync → ScheduleNewOrchestrationInstanceAsync, RetryOptions + CallActivityWithRetryAsync → TaskOptions on the normal CallActivityAsync, and entities become classes dispatched with TaskEntityDispatcher. Two defaults flip: ContinueAsNew now preserves unprocessed events, and RestartAsync no longer generates a new instance ID. Entity proxies and cross-task-hub overloads are gone, and orchestration history moves to GetOrchestrationHistoryAsync. Your storage provider settings are unchanged.
  • Cold starts are no longer tuned for you; the worker guide lists optimisations such as ReadyToRun. On Flex Consumption, always-ready instances remove the problem for a price.

Run func start with Azure Functions Core Tools 4.x and exercise every trigger before you deploy.

Deploy without an outage

Two things have to change in Azure, and they can't change in the same instant: the FUNCTIONS_WORKER_RUNTIME setting to dotnet-isolated, and the deployed code. Between the two, the app logs AZFD0013, a runtime and payload mismatch, and fails requests. So don't do it in the production slot.

  1. Create a staging slot (Premium and Dedicated plans have them; a Consumption plan allows one extra).
  2. In the staging slot only, set FUNCTIONS_WORKER_RUNTIME to dotnet-isolated. Don't mark it as a slot setting, so it travels with the swap. If you're also changing .NET version, update the stack configuration too, and update any pipeline or Bicep that sets either value, or the next deployment puts them back.
  3. Publish the migrated project to the staging slot. Visual Studio offers to update the runtime setting during publish; it's still two operations, and the staging logs show the mismatch error briefly.
  4. Test in the slot until the errors stop and every trigger works.
  5. Swap. A swap is one atomic update, so production never sees the mismatch.
  6. Verify production, then repeat for the next app on your list.
bash
az functionapp config appsettings set --name <app> --resource-group <rg> --slot staging \
  --settings FUNCTIONS_WORKER_RUNTIME=dotnet-isolated
az functionapp config set --name <app> --resource-group <rg> --slot staging --net-framework-version v10.0

If the app runs on Linux Consumption, there's a second deadline behind this one: that plan retires on 30 September 2028, and .NET 10 can't run on it at all. Our guide to Azure Functions hosting plans explains the move to Flex Consumption, which only accepts the isolated model, so this migration comes first.

Sources

Did this fix it?

Comments

No comments yet. Share what worked for you.

Sign in to comment. Comments show under a display name you choose, never your email.