Migrar su aplicación Durable Functions del modelo en proceso a un modelo de trabajo aislado (.NET)

Esta guía le guía a través de la migración de la aplicación de .NET Durable Functions desde el modelo en proceso al modelo de trabajo aislado. El modelo en proceso llega al final del soporte técnico el 10 de noviembre de 2026. Después de esa fecha, no se proporcionan actualizaciones de seguridad ni correcciones de errores. El modelo de trabajador aislado también proporciona control total del proceso, inyección de dependencias estándar de .NET y acceso a las funcionalidades más recientes de la plataforma.

Advertencia

La compatibilidad con el modelo en proceso finaliza el 10 de noviembre de 2026. Se recomienda migrar ahora. Para obtener información general sobre el modelo de trabajo aislado, consulte .NET información general sobre el proceso de trabajo aislado.

Prerrequisitos

  • Azure Functions Core Tools v4.x o posterior
  • SDK de .NET 8.0 (o la versión de .NET de destino)
  • Visual Studio 2022 o VS Code with Azure Functions extension

Información general sobre la migración

En un nivel alto, la migración requiere estos pasos:

  1. Actualizar el archivo del proyecto : cambie a la salida ejecutable, reemplace los paquetes.
  2. Agregar Program.cs : cree el punto de entrada del host.
  3. Actualizar referencias de paquete : intercambio de paquetes en proceso para equivalentes aislados
  4. Actualizar código de función : cambiar atributos, tipos y espacios de nombres
  5. Actualizar local.settings.json : establezca el entorno de ejecución en dotnet-isolated
  6. Probar localmente y desplegar en Azure

Lista de comprobación de referencia rápida

Use esta lista de comprobación para realizar un seguimiento del progreso. Una versión detallada está al final de esta guía.

  • [ ] Archivo del proyecto: se agregó <OutputType>Exe</OutputType>, paquetes reemplazados
  • [ ] Creado Program.cs, eliminado FunctionsStartup
  • [ ] Se ha actualizado [FunctionName][Function], se han reemplazado los tipos de contexto o cliente.
  • [ ] Se han quitado todas las referencias de Microsoft.Azure.WebJobs.*
  • [ ] Actualizado local.settings.json a dotnet-isolated
  • [ ] Probado localmente e implementado

Identificación de aplicaciones para migrar (opcional)

Si no está seguro de qué aplicaciones siguen usando el modelo en proceso, ejecute este script Azure PowerShell:

$FunctionApps = Get-AzFunctionApp

$AppInfo = @{}

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

$AppInfo

Las aplicaciones que muestran dotnet como tiempo de ejecución usan el modelo de proceso. Las aplicaciones que ya muestran dotnet-isolated usan el modelo de trabajo aislado.

Actualización del archivo del proyecto

Antes (en proceso)

<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>

Después (trabajador aislado)

<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>

Los cambios principales son cambiar a un tipo de salida ejecutable y reemplazar todos los paquetes Microsoft.Azure.WebJobs.* por sus equivalentes Microsoft.Azure.Functions.Worker.*.

Agregar Program.cs

El modelo de trabajador aislado requiere un Program.cs punto de entrada. Cree este archivo en la raíz del proyecto. Si tiene una FunctionsStartup clase en Startup.cs, mueva esos registros de servicio al ConfigureServices bloque y elimine 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();

Actualización de las referencias del paquete

mapeo de paquetes de Durable Functions

Paquete en proceso Paquete de trabajador aislado
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

Mapeo de paquetes de extensión comunes

En proceso Trabajador aislado
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

Elimine las referencias a espacios de nombres Microsoft.Azure.WebJobs.* y Microsoft.Azure.Functions.Extensions del proyecto.

Actualizar código de función

En esta sección se tratan los cambios de código para cada tipo de Durable Functions. Vaya a la sección de los tipos de función que usa la aplicación:

Para obtener una descripción detallada de cada API, consulte la referencia de API.

Cambios en el espacio de nombres

// 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;

Cambios en el atributo de función

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

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

Cambios de función de Orchestrator

Antes (En proceso):

[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 };
}

Después (trabajo aislado):

[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 };
}

Diferencias clave

Aspecto En proceso Trabajador aislado
Tipo de contexto IDurableOrchestrationContext TaskOrchestrationContext
Logger Parámetro ILogger context.CreateReplaySafeLogger()
Atributo [FunctionName] [Function]

Cambios en la función de actividad

Antes (En proceso):

[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;
}

Después (trabajo aislado):

[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;
}

Cambios en la función de cliente

Antes (En proceso):

[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);
}

Después (trabajo aislado):

[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);
}

Cambios en el tipo de cliente

En proceso Trabajador aislado
IDurableOrchestrationClient DurableTaskClient
StartNewAsync() ScheduleNewOrchestrationInstanceAsync()
CreateCheckStatusResponse() CreateCheckStatusResponseAsync()
HttpRequest / IActionResult HttpRequestData / HttpResponseData

Cambios en la política de reintento

En proceso usa RetryOptions con CallActivityWithRetryAsync. El trabajador aislado utiliza TaskOptions con el estándar CallActivityAsync.

Antes (En proceso):

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

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

Después (trabajo aislado):

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

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

Cambios en la función de entidad

Antes (En proceso):

[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;
    }
}

Después (trabajo aislado):

[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;
}

Cambios de comportamiento disruptivos

Revise estos cambios antes de probar la aplicación migrada. Para ver el mapeo completo de cada API, consulte la documentación de API.

Advertencia

Se ha cambiado el valor predeterminado de serialización: el trabajo aislado usa System.Text.Json de forma predeterminada en lugar de Newtonsoft.Json. Si las orquestaciones pasan objetos complejos, realice pruebas cuidadosas de la serialización. Consulte Diferencias de serialización de JSON para conocer las opciones de configuración.

Advertencia

ContinueAsNuevo cambio predeterminado: el preserveUnprocessedEvents parámetro predeterminado cambió de false (2.x) a true (aislado). Si la orquestación utiliza ContinueAsNew y depende de que se descarten los eventos no procesados, pase preserveUnprocessedEvents: false explícitamente.

Nota:

Cambio predeterminado restartAsync: el parámetro restartWithNewInstanceId, cuyo valor predeterminado era true (2.x), ha cambiado a false (aislado). Si el código llama a RestartAsync y depende de que se genere un nuevo identificador de instancia, pase restartWithNewInstanceId: true explícitamente.

Otros cambios importantes:

  • Proxies de entidad eliminadosCreateEntityProxy<T> no está disponible. Use Entities.CallEntityAsync o Entities.SignalEntityAsync directamente.
  • Operaciones entre centros de tareas quitadastaskHubName/connectionName: las sobrecargas aceptadas no están disponibles. Solo se admiten las operaciones del mismo centro de tareas.
  • Historial de orquestaciones movido : DurableOrchestrationStatus.History ya no está en el objeto de estado. Utilice DurableTaskClient.GetOrchestrationHistoryAsync.

Actualizar local.settings.json

El cambio de clave es establecer FUNCTIONS_WORKER_RUNTIME de dotnet a dotnet-isolated:

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

Nota:

La migración no modifica la configuración del back-end de almacenamiento (Azure Storage, MSSQL, Netherite o Durable Task Scheduler). Mantenga la configuración relacionada con el almacenamiento existente.

Probar localmente

Ejecute la aplicación de funciones localmente y compruebe que todas las orquestaciones, actividades y entidades funcionan correctamente.

func start

Comprobación de la funcionalidad

Pruebe los escenarios siguientes según corresponda:

  1. Inicio de una orquestación con un desencadenador HTTP
  2. Supervisión del estado de la orquestación
  3. Comprobación del orden de ejecución de la actividad
  4. Prueba de operaciones de entidad si procede
  5. Comprobación de la telemetría de Application Insights

Implementar en Azure

Utiliza espacios de implementación para minimizar el tiempo de inactividad:

  1. Cree un entorno de ensayo para su aplicación de funciones.
  2. Actualización de la configuración del slot de puesta en escena:
    • Establece FUNCTIONS_WORKER_RUNTIME en dotnet-isolated.
    • Actualice la versión del stack de .NET si es necesario.
  3. Implemente el código migrado en la ranura de ensayo.
  4. Pruebe exhaustivamente en el intervalo de ensayo.
  5. Realice el intercambio de intervalos para implantar cambios en producción.

Actualización de la configuración de la aplicación

En el portal de Azure o a través de la CLI:

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

Actualización de la configuración de la pila

Si tiene como destino una versión de .NET diferente:

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

Problemas comunes de migración

Problema: Errores de carga de ensamblados

Síntoma:Could not load file or assembly errores.

Solución: Asegúrese de quitar todas las referencias de paquete Microsoft.Azure.WebJobs.* y reemplazarlas por equivalentes de trabajadores aislados.

Problema: No se encontró el atributo de enlace

Síntoma:The type or namespace 'QueueTrigger' could not be found

Solución: agregue el paquete de extensión adecuado y actualice con las instrucciones:

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

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

Problema: No se encontró IDurableOrchestrationContext

Síntoma:The type or namespace 'IDurableOrchestrationContext' could not be found

Solución: Reemplace por TaskOrchestrationContext:

using Microsoft.DurableTask;

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

Problema: diferencias de serialización JSON

Síntoma: Errores de serialización o formatos de datos inesperados

Solución: El modelo aislado usa System.Text.Json de forma predeterminada. Configure la serialización en Program.cs:

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

Para usar Newtonsoft.Json en su lugar:

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

Problema: Migración de la configuración de serialización personalizada

Síntoma: Ha usado IMessageSerializerSettingsFactory en el modelo en-proceso y necesita el equivalente en el trabajador aislado.

Solución: Configure el serializador a nivel de trabajador en Program.cs. Para obtener más información, consulte la sección cambios de comportamiento de la referencia de la API y Serialización y persistencia en Funciones Duraderas.

Para usar Newtonsoft.Json con la configuración personalizada:

// 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();

Nota:

Este enfoque requiere los paquetes NuGet Newtonsoft.Json y Azure.Core.Serialization.

Lista de comprobación

Use esta lista de comprobación para garantizar una migración completa:

  • Archivo de proyecto actualizado con <OutputType>Exe</OutputType>
  • Se ha reemplazado Microsoft.NET.Sdk.Functions por paquetes de trabajo
  • Se ha reemplazado Microsoft.Azure.WebJobs.Extensions.DurableTask por un paquete aislado
  • Creado Program.cs con la configuración del host
  • Clase eliminada FunctionsStartup (si está presente)
  • Se ha actualizado todo [FunctionName] a [Function]
  • Reemplazado por IDurableOrchestrationContextTaskOrchestrationContext
  • Reemplazado por IDurableOrchestrationClientDurableTaskClient
  • Registro actualizado para usar DI o FunctionContext
  • Se ha actualizado local.settings.json con el entorno de ejecución dotnet-isolated
  • Se han quitado todo Microsoft.Azure.WebJobs.* con instrucciones
  • Se han agregado Microsoft.Azure.Functions.Worker instrucciones using
  • Se ha reemplazado CreateEntityProxy<T> por las llamadas directas a CallEntityAsync/SignalEntityAsync
  • Sobrecargas de operación de centros de tareas cruzados reemplazadas (si se utilizan)
  • Se han reemplazado las llamadas por ID GetStatusAsync/PurgeInstanceHistoryAsync por lotes por llamadas individuales o basadas en filtros
  • Acceso migrado DurableOrchestrationStatus.History a GetOrchestrationHistoryAsync
  • Se han actualizado los parámetros del constructor DispatchAsync de la entidad para usar DI
  • Prueba local de todas las funciones
  • Se ha implementado en el intervalo de ensayo y comprobado
  • Se ha cambiado a producción

Pasos siguientes