Migrare l'app di Durable Functions dal modello in-process al modello di worker isolato (.NET)

Questa guida illustra come eseguire la migrazione dell'app .NET Durable Functions dal modello in-process al modello di lavoro isolato. Il modello in-process raggiunge la fine del supporto il 10 novembre 2026. Dopo tale data, non vengono forniti aggiornamenti della sicurezza o correzioni di bug. Il modello di lavoro isolato offre anche il controllo completo dei processi, l'inserimento di dipendenze standard .NET e l'accesso alle funzionalità della piattaforma più recenti.

Avvertimento

Il supporto per il modello in-process termina il 10 novembre 2026. È consigliabile eseguire la migrazione ora. Per informazioni generali sul modello di lavoro isolato, vedere .NET panoramica del processo di lavoro isolato.

Elenco di controllo per la migrazione

Usare l'elenco di controllo seguente per tenere traccia dello stato di avanzamento in ogni passaggio della migrazione:

Passo Sezione
1. Verificare i prerequisiti Prerequisiti
2. Aggiornare il file di progetto Aggiornare il file di progetto
3. Aggiungere Program.cs Aggiungere Program.cs
4. Aggiornare i riferimenti ai pacchetti Aggiornare i riferimenti ai pacchetti
5. Aggiornare il codice della funzione Aggiornare il codice della funzione
6. Aggiornare local.settings.json Aggiornare local.settings.json
7. Testare localmente Testare localmente
8. Eseguire la distribuzione in Azure Distribuzione in Azure

Prerequisiti

  • Funzioni di Azure Core Tools v4.x o versione successiva
  • .NET 8.0 SDK (o versione di .NET di destinazione)
  • Visual Studio 2022 o VS Code with Funzioni di Azure extension

Identificare le app di cui eseguire la migrazione (facoltativo)

Se non si è certi delle app che usano ancora il modello in-process, eseguire questo script Azure PowerShell:

$FunctionApps = Get-AzFunctionApp

$AppInfo = @{}

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

$AppInfo

Le app che mostrano dotnet come runtime usano il modello in-process. Le app che mostrano dotnet-isolated già usano il modello di lavoro isolato.

Aggiornare il file di progetto

Prima (in-process)

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
    <AzureFunctionsVersion>v4</AzureFunctionsVersion>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Microsoft.NET.Sdk.Functions" Version="4.1.1" />
    <PackageReference Include="Microsoft.Azure.WebJobs.Extensions.DurableTask" Version="2.13.0" />
  </ItemGroup>
</Project>

Dopo (worker isolato)

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
    <AzureFunctionsVersion>v4</AzureFunctionsVersion>
    <OutputType>Exe</OutputType>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>
  <ItemGroup>
    <FrameworkReference Include="Microsoft.AspNetCore.App" />
    <PackageReference Include="Microsoft.Azure.Functions.Worker" Version="1.21.0" />
    <PackageReference Include="Microsoft.Azure.Functions.Worker.Sdk" Version="1.17.2" />
    <PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore" Version="1.2.1" />
    <PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.DurableTask" Version="1.14.1" />
    <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>

Le modifiche principali sono il passaggio a un tipo di output eseguibile e la sostituzione di tutti i pacchetti Microsoft.Azure.WebJobs.* con i relativi equivalenti Microsoft.Azure.Functions.Worker.*.

Aggiungere Program.cs

Il modello di lavoratore isolato richiede un punto di ingresso Program.cs. Creare questo file nella radice del progetto. Se si dispone una FunctionsStartup classe in Startup.cs, spostare le registrazioni di servizio nel ConfigureServices blocco ed eliminare Startup.cs.

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

var host = new HostBuilder()
    .ConfigureFunctionsWebApplication()
    .ConfigureServices(services => {
        services.AddApplicationInsightsTelemetryWorkerService();
        services.ConfigureFunctionsApplicationInsights();
        
        // Add your custom services here (previously in FunctionsStartup)
        // services.AddSingleton<IMyService, MyService>();
    })
    .Build();

host.Run();

Aggiornare i riferimenti ai pacchetti

Mapping dei pacchetti Durable Functions

Pacchetto in corso Pacchetto di lavoro isolato
Microsoft.Azure.WebJobs.Extensions.DurableTask Microsoft.Azure.Functions.Worker.Extensions.DurableTask
Microsoft.DurableTask.SqlServer.AzureFunctions Microsoft.Azure.Functions.Worker.Extensions.DurableTask.SqlServer
Microsoft.Azure.DurableTask.Netherite.AzureFunctions Microsoft.Azure.Functions.Worker.Extensions.DurableTask.Netherite

Mappatura dei pacchetti di estensione comuni

In corso Lavoro isolato
Microsoft.Azure.WebJobs.Extensions.Storage Microsoft.Azure.Functions.Worker.Extensions.Storage.Blobs, .Queues, .Tables
Microsoft.Azure.WebJobs.Extensions.CosmosDB Microsoft.Azure.Functions.Worker.Extensions.CosmosDB
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.EventGrid Microsoft.Azure.Functions.Worker.Extensions.EventGrid

Importante

Rimuovere eventuali riferimenti agli spazi dei nomi Microsoft.Azure.WebJobs.* e Microsoft.Azure.Functions.Extensions dal progetto.

Aggiornare il codice della funzione

Questa sezione illustra le modifiche al codice per ogni tipo di Durable Functions. Passare alla sezione per i tipi di funzione usati dall'app:

Per un mapping completo dell'API per API, vedere le informazioni di riferimento sulle API.

Modifiche allo spazio dei nomi

// Before (In-Process)
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.DurableTask;
using Microsoft.Azure.WebJobs.Extensions.Http;

// After (Isolated Worker)
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Http;
using Microsoft.DurableTask;
using Microsoft.DurableTask.Client;
using Microsoft.DurableTask.Entities;

Modifiche dell'attributo della funzione

// Before (In-Process)
[FunctionName("MyOrchestrator")]

// After (Isolated Worker)
[Function(nameof(MyOrchestrator))]

Modifiche delle funzioni dell'agente di orchestrazione

Prima (in-process):

[FunctionName("OrderOrchestrator")]
public static async Task<OrderResult> RunOrchestrator(
    [OrchestrationTrigger] IDurableOrchestrationContext context,
    ILogger log)
{
    var order = context.GetInput<Order>();
    
    await context.CallActivityAsync("ValidateOrder", order);
    await context.CallActivityAsync("ProcessPayment", order.Payment);
    await context.CallActivityAsync("ShipOrder", order);
    
    return new OrderResult { Success = true };
}

Dopo (Lavoratore isolato):

[Function(nameof(OrderOrchestrator))]
public static async Task<OrderResult> OrderOrchestrator(
    [OrchestrationTrigger] TaskOrchestrationContext context)
{
    ILogger logger = context.CreateReplaySafeLogger(nameof(OrderOrchestrator));
    var order = context.GetInput<Order>();
    
    await context.CallActivityAsync("ValidateOrder", order);
    await context.CallActivityAsync("ProcessPayment", order.Payment);
    await context.CallActivityAsync("ShipOrder", order);
    
    return new OrderResult { Success = true };
}

Differenze principali

Aspetto In lavorazione Lavoro isolato
Tipo di contesto IDurableOrchestrationContext TaskOrchestrationContext
Logger parametro ILogger context.CreateReplaySafeLogger()
Attribute [FunctionName] [Function]

Modifiche delle funzioni di attività

Prima (in-process):

[FunctionName("ValidateOrder")]
public static bool ValidateOrder(
    [ActivityTrigger] Order order,
    ILogger log)
{
    log.LogInformation("Validating order {OrderId}", order.Id);
    return order.Items.Any() && order.TotalAmount > 0;
}

Dopo (Lavoratore isolato):

[Function(nameof(ValidateOrder))]
public static bool ValidateOrder(
    [ActivityTrigger] Order order,
    FunctionContext executionContext)
{
    ILogger logger = executionContext.GetLogger(nameof(ValidateOrder));
    logger.LogInformation("Validating order {OrderId}", order.Id);
    return order.Items.Any() && order.TotalAmount > 0;
}

Modifiche della funzione client

Prima (in-process):

[FunctionName("StartOrder")]
public static async Task<IActionResult> StartOrder(
    [HttpTrigger(AuthorizationLevel.Function, "post")] HttpRequest req,
    [DurableClient] IDurableOrchestrationClient client,
    ILogger log)
{
    var order = await req.ReadFromJsonAsync<Order>();
    string instanceId = await client.StartNewAsync("OrderOrchestrator", order);
    
    return client.CreateCheckStatusResponse(req, instanceId);
}

Dopo (Lavoratore isolato):

[Function("StartOrder")]
public static async Task<HttpResponseData> StartOrder(
    [HttpTrigger(AuthorizationLevel.Function, "post")] HttpRequestData req,
    [DurableClient] DurableTaskClient client,
    FunctionContext executionContext)
{
    ILogger logger = executionContext.GetLogger("StartOrder");
    var order = await req.ReadFromJsonAsync<Order>();
    string instanceId = await client.ScheduleNewOrchestrationInstanceAsync(
        nameof(OrderOrchestrator), 
        order
    );
    
    return await client.CreateCheckStatusResponseAsync(req, instanceId);
}

Modifiche al tipo di client

In corso Lavoro isolato
IDurableOrchestrationClient DurableTaskClient
StartNewAsync() ScheduleNewOrchestrationInstanceAsync()
CreateCheckStatusResponse() CreateCheckStatusResponseAsync()
HttpRequest / IActionResult HttpRequestData / HttpResponseData

Modifiche ai criteri di ripetizione dei tentativi

In-process usa RetryOptions con CallActivityWithRetryAsync. Il lavoratore isolato usa TaskOptions con lo standard CallActivityAsync.

Prima (in-process):

var retryOptions = new RetryOptions(
    firstRetryInterval: TimeSpan.FromSeconds(5),
    maxNumberOfAttempts: 3);

string result = await context.CallActivityWithRetryAsync<string>(
    "MyActivity", retryOptions, input);

Dopo (Lavoratore isolato):

var retryOptions = new TaskOptions(
    new TaskRetryOptions(new RetryPolicy(
        maxNumberOfAttempts: 3,
        firstRetryInterval: TimeSpan.FromSeconds(5))));

string result = await context.CallActivityAsync<string>(
    "MyActivity", input, retryOptions);

Modifiche alle funzioni di entità

Prima (in-process):

[FunctionName(nameof(Counter))]
public static void Counter([EntityTrigger] IDurableEntityContext ctx)
{
    switch (ctx.OperationName.ToLowerInvariant())
    {
        case "add":
            ctx.SetState(ctx.GetState<int>() + ctx.GetInput<int>());
            break;
        case "get":
            ctx.Return(ctx.GetState<int>());
            break;
    }
}

Dopo (Lavoratore isolato):

[Function(nameof(Counter))]
public static Task Counter([EntityTrigger] TaskEntityDispatcher dispatcher)
{
    return dispatcher.DispatchAsync<CounterEntity>();
}

public class CounterEntity
{
    public int Value { get; set; }
    
    public void Add(int amount) => Value += amount;
    public int Get() => Value;
}

Modifiche importanti del comportamento

Esaminare queste modifiche prima di testare l'app migrata. Per il mapping completo dell'API per API, vedere le informazioni di riferimento sull'API.

Avvertimento

Serializzazione predefinita modificata: il lavoratore isolato usa System.Text.Json per impostazione predefinita anziché Newtonsoft.Json. Se le tue orchestrazioni superano oggetti complessi, testare attentamente la serializzazione. Vedere Differenze di serializzazione JSON per le opzioni di configurazione.

Avvertimento

ContinueAsNuova modifica predefinita: il preserveUnprocessedEvents parametro predefinito è stato modificato da false (2.x) a true (isolato). Se l'orchestrazione usa ContinueAsNew e si basa su eventi non elaborati rimossi, passare preserveUnprocessedEvents: false in modo esplicito.

Annotazioni

Cambio del valore predefinito di RestartAsync: il parametro predefinito è stato modificato da restartWithNewInstanceId (2.x) a true (isolato). Se il codice chiama RestartAsync e dipende da un nuovo ID istanza generato, passare restartWithNewInstanceId: true in modo esplicito.

Altre modifiche rilevanti:

  • Proxy di entità rimossiCreateEntityProxy<T> non è disponibile. Usare Entities.CallEntityAsync o Entities.SignalEntityAsync direttamente.
  • Operazioni cross-task-hub rimosse — gli overload che accettavano taskHubName/connectionName non sono disponibili. Sono supportate solo le operazioni dello stesso task hub.
  • Cronologia orchestrazione spostataDurableOrchestrationStatus.History non è più presente nell'oggetto di stato. Utilizzare il DurableTaskClient.GetOrchestrationHistoryAsync.

Aggiornare local.settings.json

La modifica della chiave viene impostata FUNCTIONS_WORKER_RUNTIME da dotnet a dotnet-isolated:

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

Annotazioni

La configurazione back-end di archiviazione (Archiviazione di Azure, MSSQL, Netherite o Durable Task Scheduler) rimane invariata dalla migrazione. Mantenere le impostazioni correlate all'archiviazione esistenti.

Testare localmente

Esegui l'applicazione delle funzioni localmente e verifica che tutte le orchestrazioni, le attività e le entità funzionino correttamente.

func start

Verificare la funzionalità

Testare gli scenari seguenti in base alle esigenze:

  1. Avviare un'orchestrazione con un trigger HTTP
  2. Monitorare lo stato di orchestrazione
  3. Verificare l'ordine di esecuzione dell'attività
  4. Testare le operazioni di entità, se applicabile
  5. Controllare i dati di telemetria di Application Insights

Distribuzione su Azure

Usare gli slot di distribuzione per minimizzare il downtime

  1. Creare uno slot di staging per l'app per le funzioni.
  2. Aggiornare la configurazione dello slot di staging:
    • Impostare FUNCTIONS_WORKER_RUNTIME su dotnet-isolated.
    • Aggiornare .NET versione dello stack, se necessario.
  3. Distribuire il codice migrato nello slot di staging.
  4. Eseguire un test approfondito nello slot di staging.
  5. Eseguire lo scambio di slot per spostare le modifiche nell'ambiente di produzione.

Aggiornare le impostazioni dell'applicazione

Nel portale di Azure o tramite l'interfaccia della riga di comando:

az functionapp config appsettings set \
    --name <FUNCTION_APP_NAME> \
    --resource-group <RESOURCE_GROUP> \
    --settings FUNCTIONS_WORKER_RUNTIME=dotnet-isolated

Aggiornare la configurazione dello stack

Se la destinazione è una versione .NET diversa:

az functionapp config set \
    --name <FUNCTION_APP_NAME> \
    --resource-group <RESOURCE_GROUP> \
    --net-framework-version v8.0

Problemi comuni relativi alla migrazione

Problema: Errori di caricamento dell'assembly

Sintomo:Could not load file or assembly errori.

Soluzione: rimuovere tutti i Microsoft.Azure.WebJobs.* riferimenti al pacchetto e sostituirli con processi di lavoro isolati equivalenti.

Problema: Impossibile trovare l'attributo di associazione

Sintomo:The type or namespace 'QueueTrigger' could not be found

Soluzione: aggiungere il pacchetto di estensione appropriato e aggiornare le istruzioni using:

// Add using statement
using Microsoft.Azure.Functions.Worker;

// Install package
// dotnet add package Microsoft.Azure.Functions.Worker.Extensions.Storage.Queues

Problema: IDurableOrchestrationContext non trovato

Sintomo:The type or namespace 'IDurableOrchestrationContext' could not be found

Soluzione: Sostituire con TaskOrchestrationContext:

using Microsoft.DurableTask;

[Function(nameof(MyOrchestrator))]
public static async Task MyOrchestrator([OrchestrationTrigger] TaskOrchestrationContext context)
{
    // ...
}

Problema: differenze di serializzazione JSON

Sintomo: Errori di serializzazione o formati di dati imprevisti

Soluzione: Il modello isolato utilizza System.Text.Json per impostazione predefinita. Configurare la serializzazione in Program.cs:

var host = new HostBuilder()
    .ConfigureFunctionsWebApplication()
    .ConfigureServices(services => {
        services.Configure<JsonSerializerOptions>(options => {
            options.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
        });
    })
    .Build();

Per usare invece Newtonsoft.Json:

services.Configure<WorkerOptions>(options => {
    options.Serializer = new NewtonsoftJsonObjectSerializer();
});

Problema: Migrazione delle impostazioni di serializzazione personalizzate

Sintomo: hai usato IMessageSerializerSettingsFactory nel modello in-process ed è necessario l'equivalente nel processo di lavoro isolato.

Soluzione: configurare il serializzatore a livello di worker in Program.cs. Per informazioni dettagliate, vedere la sezione delle modifiche comportamentali della documentazione API e Serializzazione e persistenza in Durable Functions.

Per usare Newtonsoft.Json con impostazioni personalizzate:

// Program.cs
var host = new HostBuilder()
    .ConfigureFunctionsWebApplication()
    .ConfigureServices(services =>
    {
        services.Configure<WorkerOptions>(options =>
        {
            var settings = new JsonSerializerSettings
            {
                TypeNameHandling = TypeNameHandling.None,
                DateFormatHandling = DateFormatHandling.IsoDateFormat,
            };
            options.Serializer = new NewtonsoftJsonObjectSerializer(settings);
        });
    })
    .Build();

Annotazioni

Questo approccio richiede i pacchetti NuGet Newtonsoft.Json e Azure.Core.Serialization.

Checklist

Usare questo elenco di controllo per garantire una migrazione completa:

  • File di progetto aggiornato con <OutputType>Exe</OutputType>
  • Sostituito Microsoft.NET.Sdk.Functions con i pacchetti di lavoro
  • Sostituito Microsoft.Azure.WebJobs.Extensions.DurableTask con un pacchetto isolato
  • Creato Program.cs con la configurazione dell'host
  • Classe rimossa FunctionsStartup (se presente)
  • Aggiornati tutti [FunctionName] a [Function]
  • Sostituito IDurableOrchestrationContext con TaskOrchestrationContext
  • Sostituito IDurableOrchestrationClient con DurableTaskClient
  • Registrazione aggiornata per l'uso di DI o FunctionContext
  • Aggiornato il runtime di local.settings.json con dotnet-isolated
  • Sono state rimosse tutte le istruzioni using di Microsoft.Azure.WebJobs.*
  • Sono state aggiunte istruzioni using di Microsoft.Azure.Functions.Worker
  • Sostituito CreateEntityProxy<T> con chiamate dirette CallEntityAsync/SignalEntityAsync
  • Sostituzione degli overload delle operazioni tra hub di attività (se in uso)
  • Sostituite le chiamate batch GetStatusAsync/PurgeInstanceHistoryAsync per ID con chiamate singole o basate su filtro
  • Accesso migrato DurableOrchestrationStatus.History a GetOrchestrationHistoryAsync
  • Parametri del costruttore DispatchAsync di entità aggiornati per l'uso dell'inserimento delle dipendenze
  • Tutte le funzioni sono state testate in locale
  • Distribuito nello slot di staging e verificato
  • Scambiato in produzione

Passaggi successivi