Azure Functions with .NET now means one programming model: the isolated worker. The older in-process model reaches end of support on November 10, 2026, the same day that .NET 8 and .NET 9 leave support. Many .NET function apps therefore face a migration this autumn, both to the isolated model and to .NET 10 LTS. This guide explains how the isolated worker model works and walks through triggers and bindings, dependency injection and middleware, and the Flex Consumption plan. It also covers every major Durable Functions pattern, cold start mitigation and local development, with production-quality C# throughout.

What Is Azure Functions for .NET Developers?#

Azure Functions is Microsoft's event-driven serverless platform. You write methods that react to triggers, such as HTTP requests, queue messages, timers or blob uploads. The platform handles hosting, scaling and the plumbing for input and output bindings. For .NET, three changes define the current state of the platform:

  • The isolated worker is the only supported model going forward. Your functions run in a separate .NET process that you control. The in-process model, which only supports .NET 8, loses support on November 10, 2026.
  • Isolated apps support .NET 10, .NET 9, .NET 8 and .NET Framework 4.8 on Functions runtime 4.x. Because .NET 8 and .NET 9 also leave support that day, .NET 10 LTS is the practical target, and it is supported until November 2028.
  • Flex Consumption is the recommended serverless plan. The classic Consumption plan is now labeled legacy. Linux Consumption receives no new language versions and retires on September 30, 2028, so .NET 10 apps on Linux must use Flex Consumption or another plan.

There is also a new build SDK. Projects now declare Azure.Functions.Sdk as their MSBuild project SDK instead of referencing the Microsoft.Azure.Functions.Worker.Sdk package. The new SDK sets the Functions version, output type and source generators for you, and it lets dotnet run start the Functions host when Core Tools are installed.

How the Isolated Worker Model Works#

Every function app has two processes. The Functions host owns triggers, scaling decisions, bindings and the connection to Azure services. The worker is your .NET console app. It starts a generic host, registers services and middleware, and executes function invocations that the host sends over gRPC.

This split explains the model's behavior:

  • You own the process. Program.cs builds a normal .NET host, so dependency injection, configuration, logging, options validation and IHttpClientFactory work as they do in ASP.NET Core.
  • Your dependencies cannot conflict with the host's. In-process apps had to match the host's assembly versions. Isolated apps can use any version of System.Text.Json, the Azure SDKs or EF Core.
  • Bindings are declarative metadata. Attributes such as [ServiceBusTrigger] come from worker extension packages named Microsoft.Azure.Functions.Worker.Extensions.*. Source generators emit function metadata at build time, so the host knows what to listen to before your code runs.
  • HTTP can use real ASP.NET Core types. With ASP.NET Core integration, HTTP triggers flow through ASP.NET Core in the worker, so your functions accept HttpRequest and return IActionResult.

Getting Started with a .NET 10 Isolated Function App#

Install Azure Functions Core Tools v4, then create and run a project:

Bash
func init Shop.Functions --worker-runtime dotnet-isolated --target-framework net10.0
cd Shop.Functions
func new --template "Http Trigger" --name SubmitOrder
func start    # or: dotnet run

A modern project file uses the new SDK and references only the worker and the extensions you need:

XML
<Project Sdk="Azure.Functions.Sdk/1.0.1">
  <PropertyGroup>
    <TargetFramework>net10.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
  </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.1" />
    <PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.ServiceBus"
                      Version="5.24.0" />
    <PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.DurableTask"
                      Version="1.19.1" />
    <PackageReference Include="Microsoft.Azure.Functions.Worker.OpenTelemetry" Version="1.2.0" />
    <PackageReference Include="Azure.Monitor.OpenTelemetry.Exporter" Version="1.9.0" />
  </ItemGroup>
</Project>

Program.cs uses FunctionsApplication.CreateBuilder, which returns an IHostApplicationBuilder and adds the Functions defaults, including converters, logging integration, output binding support and gRPC:

C#
using Azure.Monitor.OpenTelemetry.Exporter;
using Microsoft.Azure.Functions.Worker.Builder;
using Microsoft.Azure.Functions.Worker.OpenTelemetry;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

var builder = FunctionsApplication.CreateBuilder(args);

builder.ConfigureFunctionsWebApplication();     // ASP.NET Core integration for HTTP
builder.UseMiddleware<TimingMiddleware>();

var otel = builder.Services.AddOpenTelemetry().UseFunctionsWorkerDefaults();
if (!string.IsNullOrEmpty(builder.Configuration["APPLICATIONINSIGHTS_CONNECTION_STRING"]))
{
    otel.UseAzureMonitorExporter();
}

builder.Services.AddOptions<PricingOptions>()
    .Bind(builder.Configuration.GetSection("Pricing"))
    .ValidateDataAnnotations()
    .ValidateOnStart();

builder.Services.AddHttpClient<InventoryClient>(client =>
    client.BaseAddress = new Uri(builder.Configuration["Inventory:BaseUrl"]!));

builder.Build().Run();

For Application Insights through OpenTelemetry, also set "telemetryMode": "OpenTelemetry" in host.json so that the host and the worker emit consistent telemetry. The OpenTelemetry guide covers custom traces and metrics.

Triggers and Bindings in the Isolated Worker#

A function is any public method with a [Function] attribute. Its trigger attribute defines the event source, its return value can feed an output binding, and a CancellationToken parameter tells you when the invocation is being cancelled, for example because the client disconnected or the instance is shutting down.

When a function needs several outputs, return a custom type and mark each property with its binding. With ASP.NET Core integration, the HTTP response property needs the [HttpResult] attribute:

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

public sealed class OrderApi(ILogger<OrderApi> logger)
{
    [Function(nameof(SubmitOrder))]
    public async Task<SubmitOrderOutput> SubmitOrder(
        [HttpTrigger(AuthorizationLevel.Function, "post", Route = "orders")] HttpRequest req,
        CancellationToken cancellationToken)
    {
        var order = await req.ReadFromJsonAsync<OrderRequest>(cancellationToken);
        if (order is null || order.Lines.Count == 0)
        {
            return new SubmitOrderOutput { Result = new BadRequestObjectResult("No order lines") };
        }

        var accepted = order with { Id = Guid.CreateVersion7().ToString() };
        logger.LogInformation("Accepted order {OrderId}", accepted.Id);

        return new SubmitOrderOutput
        {
            Result = new AcceptedResult($"/api/orders/{accepted.Id}", new { accepted.Id }),
            Message = accepted     // written to the Service Bus queue
        };
    }
}

public sealed class SubmitOrderOutput
{
    [HttpResult]
    public required IActionResult Result { get; init; }

    [ServiceBusOutput("orders", Connection = "ServiceBusConnection")]
    public OrderRequest? Message { get; init; }
}

public sealed record OrderRequest(string? Id, string CustomerId, List<OrderLine> Lines);
public sealed record OrderLine(string Sku, int Quantity);

Many triggers can bind directly to Azure SDK types instead of strings or POCOs. For Service Bus, binding to ServiceBusReceivedMessage and ServiceBusMessageActions gives you explicit control over settlement. If the function throws before settling a message, Service Bus redelivers it, and it moves the message to the dead-letter queue once the maximum delivery count is reached:

C#
public sealed class OrderProcessor(IOrderService orders, ILogger<OrderProcessor> logger)
{
    [Function(nameof(ProcessOrder))]
    public async Task ProcessOrder(
        [ServiceBusTrigger("orders", Connection = "ServiceBusConnection",
            AutoCompleteMessages = false)] ServiceBusReceivedMessage message,
        ServiceBusMessageActions actions,
        CancellationToken cancellationToken)
    {
        var order = message.Body.ToObjectFromJson<OrderRequest>();
        if (order?.Id is null)
        {
            // Poison message: retrying will never succeed
            await actions.DeadLetterMessageAsync(message,
                deadLetterReason: "InvalidPayload", cancellationToken: cancellationToken);
            return;
        }

        await orders.ProcessAsync(order, cancellationToken);   // must be idempotent
        await actions.CompleteMessageAsync(message, cancellationToken);
        logger.LogInformation("Processed order {OrderId}", order.Id);
    }
}

Prefer identity-based connections to connection strings. For a connection named ServiceBusConnection, set the app setting ServiceBusConnection__fullyQualifiedNamespace to yournamespace.servicebus.windows.net, and grant the app's managed identity the matching data role. Trigger and binding settings must live in app settings or local.settings.json, because the host reads them. Custom configuration sources in Program.cs are visible only to your code. For broader messaging design, see messaging in .NET.

Error Handling and Retries by Trigger#

Retry behavior depends on the trigger, and misunderstanding it leads to either lost events or duplicate processing:

TriggerWho retriesWhere you configure it
Service BusThe extension and the broker's delivery counthost.json and the entity's maximum delivery count
Queue StorageThe extension, then a poison queuehost.json
Blob StorageThe extension, with poison blob handlinghost.json
Event GridEvent Grid delivery retriesThe event subscription
Timer, Event Hubs, Kafka, Cosmos DBFunctions runtime retry policiesAn attribute on the function

For the triggers in the last row, the runtime enforces retry policies declared with [FixedDelayRetry] or [ExponentialBackoffRetry]. The retry count is best effort: if an instance fails mid-retry, the count is lost.

C#
[Function(nameof(NightlyReconciliation))]
[ExponentialBackoffRetry(5, "00:00:05", "00:05:00")]
public async Task NightlyReconciliation(
    [TimerTrigger("0 30 2 * * *")] TimerInfo timer,   // 02:30 UTC daily (NCRONTAB)
    CancellationToken cancellationToken)
{
    await reconciliation.RunAsync(cancellationToken);
}

Timer schedules use six-field NCRONTAB expressions. On Flex Consumption, the WEBSITE_TIME_ZONE and TZ settings are not supported, so write schedules in UTC.

Dependency Injection and Middleware#

Constructor injection works in function classes exactly as it does in ASP.NET Core. Use typed HttpClient registrations, options with validation and Microsoft.Extensions.Azure for Azure SDK clients. Function classes are resolved per invocation, so scoped services behave as you would expect.

Middleware wraps every invocation, which makes it the right place for timing, correlation, exception mapping or tenant resolution. Register it with UseMiddleware<T>(), or with UseWhen<T>() to run it only for matching functions:

C#
using System.Diagnostics;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Middleware;
using Microsoft.Extensions.Logging;

public sealed class TimingMiddleware(ILogger<TimingMiddleware> logger)
    : IFunctionsWorkerMiddleware
{
    public async Task Invoke(FunctionContext context, FunctionExecutionDelegate next)
    {
        long started = Stopwatch.GetTimestamp();
        try
        {
            await next(context);
        }
        finally
        {
            logger.LogInformation("{Function} ({InvocationId}) took {ElapsedMs:F0} ms",
                context.FunctionDefinition.Name, context.InvocationId,
                Stopwatch.GetElapsedTime(started).TotalMilliseconds);
        }
    }
}

UseMiddleware<T>() registers the middleware type as a singleton. Inject only singleton-safe dependencies into its constructor, and resolve scoped services from context.InstanceServices inside Invoke. The dependency injection guide explains lifetime rules in depth.

Choosing a Hosting Plan: Flex Consumption and Beyond#

PlanScalingCold startsNetworkingBest fit
Flex ConsumptionPer-function, scale to zero, up to 1,000 instancesReduced; optional always-ready instancesVNet integrationDefault choice for new serverless apps on Linux
PremiumElastic, always at least one warm instanceAlways-ready and prewarmed instances avoid themVNet integrationSteady load, long executions, Windows needs
Dedicated (App Service)Manual or autoscale rulesNone when always onFull App Service networkingExisting App Service capacity, predictable cost
Container AppsKEDA-based, scale to zeroContainer startupContainer Apps environmentFunctions alongside other containerized services
Consumption (legacy)Event-driven, scale to zeroFrequentNoneExisting Windows apps only

Flex Consumption is Linux-only and supports .NET 8, .NET 9 and .NET 10 isolated apps. You choose an instance memory size of 512 MB, 2,048 MB or 4,096 MB, and 2,048 MB is the recommended default. HTTP functions, Blob (Event Grid) functions and Durable Functions scale as groups, while every other function scales independently. Always-ready instances keep a baseline of warm capacity per group or function. Billing covers execution time plus any always-ready instances, with a minimum of 1,000 ms per execution.

Bash
az functionapp create --resource-group rg-shop --name func-shop-orders \
  --storage-account stshoporders --flexconsumption-location eastus \
  --runtime dotnet-isolated --runtime-version 10.0 \
  --instance-memory 2048 --always-ready-instances http=2 durable=1

Know the constraints before you commit. Flex allows one app per plan, has no deployment slots (zero-downtime rolling updates are in preview), and supports only the Azure Storage and Durable Task Scheduler backends for Durable Functions. The host times out if app initialization takes longer than 30 seconds. You also cannot migrate an existing app into Flex in place: you create a new app and redeploy. For how Functions fits next to App Service, Container Apps and AKS, see hosting .NET on Azure.

Durable Functions in the Isolated Worker#

Durable Functions adds stateful workflows on top of Functions. Orchestrator functions describe the workflow in ordinary C#. Activity functions do the actual work. The framework checkpoints progress to a storage backend and replays the orchestrator to rebuild its state after every await. In the isolated model, you use the Microsoft.Azure.Functions.Worker.Extensions.DurableTask package, TaskOrchestrationContext for orchestrators and DurableTaskClient for starting and querying instances.

Replay imposes one strict rule: orchestrator code must be deterministic. Use context.CurrentUtcDateTime instead of DateTime.UtcNow, context.NewGuid() instead of Guid.NewGuid(), and context.CreateTimer instead of Task.Delay. Never perform I/O directly in an orchestrator: put every call to a database, an HTTP service or the file system in an activity. Log through context.CreateReplaySafeLogger so that replays do not duplicate log entries.

The storage backend matters. The Durable Task Scheduler is now generally available and is the recommended backend. It is a managed service with its own dashboard and a local emulator. Azure Storage remains the zero-configuration option, MSSQL suits SQL-centric shops, and Netherite support ends on March 31, 2028.

Function Chaining and Fan-Out/Fan-In#

Chaining runs steps in sequence, and fan-out/fan-in runs activities in parallel and aggregates the results. Both are plain await code:

C#
using Microsoft.Azure.Functions.Worker;
using Microsoft.DurableTask;
using Microsoft.Extensions.Logging;

public static class FulfillmentOrchestration
{
    private static readonly TaskOptions Retry = TaskOptions.FromRetryPolicy(
        new RetryPolicy(maxNumberOfAttempts: 3, firstRetryInterval: TimeSpan.FromSeconds(5),
            backoffCoefficient: 2.0));

    [Function(nameof(FulfillOrder))]
    public static async Task<string[]> FulfillOrder(
        [OrchestrationTrigger] TaskOrchestrationContext context, OrderRequest order)
    {
        ILogger logger = context.CreateReplaySafeLogger(nameof(FulfillOrder));

        // Chaining: each step starts after the previous one completes
        await context.CallActivityAsync(nameof(FulfillmentActivities.ReserveStock), order, Retry);
        await context.CallActivityAsync(nameof(FulfillmentActivities.ChargePayment), order, Retry);

        // Fan-out/fan-in: one shipment per line, awaited together
        var shipments = order.Lines
            .Select(line => context.CallActivityAsync<string>(
                nameof(FulfillmentActivities.CreateShipment), line, Retry))
            .ToList();
        string[] trackingNumbers = await Task.WhenAll(shipments);

        logger.LogInformation("Order {OrderId} shipped in {Count} parcels",
            order.Id, trackingNumbers.Length);
        return trackingNumbers;
    }
}

public sealed class FulfillmentActivities(IInventoryService inventory,
    IPaymentService payments, IShippingService shipping)
{
    [Function(nameof(ReserveStock))]
    public Task ReserveStock([ActivityTrigger] OrderRequest order) =>
        inventory.ReserveAsync(order);

    [Function(nameof(ChargePayment))]
    public Task ChargePayment([ActivityTrigger] OrderRequest order) =>
        payments.ChargeAsync(order);

    [Function(nameof(CreateShipment))]
    public Task<string> CreateShipment([ActivityTrigger] OrderLine line) =>
        shipping.CreateShipmentAsync(line);
}

Activities are regular classes with constructor injection. Because they can be retried, make them idempotent, for example by keying payments on the order ID. Keep fan-out batches bounded: thousands of parallel activities are possible, but each adds history that the orchestrator must replay. For very large workloads, split the work into sub-orchestrations.

Async HTTP APIs and Human Interaction#

The async HTTP API pattern is built in. A starter function schedules an instance and returns 202 Accepted, with URLs for polling status, raising events and terminating the instance. The human interaction pattern combines a durable timer with an external event, so a workflow can wait hours or days for approval without holding any compute:

C#
using System.Net;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Http;
using Microsoft.DurableTask;
using Microsoft.DurableTask.Client;

public static class RefundWorkflow
{
    [Function(nameof(StartRefund))]
    public static async Task<HttpResponseData> StartRefund(
        [HttpTrigger(AuthorizationLevel.Function, "post", Route = "refunds")] HttpRequestData req,
        [DurableClient] DurableTaskClient client)
    {
        var refund = await req.ReadFromJsonAsync<RefundRequest>();
        if (refund is null)
        {
            return req.CreateResponse(HttpStatusCode.BadRequest);
        }

        string instanceId = await client.ScheduleNewOrchestrationInstanceAsync(
            nameof(RefundApproval), refund);

        // 202 Accepted with statusQueryGetUri, sendEventPostUri and terminatePostUri
        return await client.CreateCheckStatusResponseAsync(req, instanceId);
    }

    [Function(nameof(RefundApproval))]
    public static async Task<string> RefundApproval(
        [OrchestrationTrigger] TaskOrchestrationContext context, RefundRequest refund)
    {
        await context.CallActivityAsync(nameof(RefundActivities.NotifyApprover), refund);

        using var timeoutCts = new CancellationTokenSource();
        DateTime deadline = context.CurrentUtcDateTime.AddHours(24);
        Task timeout = context.CreateTimer(deadline, timeoutCts.Token);
        Task<bool> decision = context.WaitForExternalEvent<bool>("RefundDecision");

        if (await Task.WhenAny(decision, timeout) == decision)
        {
            timeoutCts.Cancel();   // cancel timers you no longer need
            return decision.Result
                ? await context.CallActivityAsync<string>(
                    nameof(RefundActivities.IssueRefund), refund)
                : "Rejected";
        }

        return await context.CallActivityAsync<string>(nameof(RefundActivities.Escalate), refund);
    }
}

An approver's UI or a Teams bot calls DurableTaskClient.RaiseEventAsync(instanceId, "RefundDecision", true), or posts to the sendEventPostUri. The monitor pattern uses the same building blocks: a loop that calls a status-check activity, sleeps with CreateTimer until the next check and exits when a condition is met or a deadline passes. For eternal monitors, call context.ContinueAsNew periodically so that history does not grow without bound.

Cold Start Mitigation#

Cold starts come from provisioning an instance, starting the host and worker, and running your startup code. You control more of that than you might think:

  • Pick the plan deliberately. Always-ready instances on Flex Consumption, or the always-ready and prewarmed instances of the Premium plan, remove cold starts for the capacity you reserve.
  • Stay current. Recent worker versions include an optimized function executor, and Microsoft recommends a framework reference to Microsoft.AspNetCore.App.
  • Enable placeholders where supported. Set WEBSITE_USE_PLACEHOLDER_DOTNETISOLATED=1 for .NET 8 or later apps that run as 64-bit processes, with netFrameworkVersion matching your target framework.
  • Publish ReadyToRun. Set PublishReadyToRun and a RuntimeIdentifier such as linux-x64 that matches the platform. This reduces JIT work at startup.
  • Keep startup lean. Avoid network calls and cache warm-ups in Program.cs, register clients lazily, and trim unused extension packages. On Flex, app initialization times out after 30 seconds.

Local Development and Testing#

Core Tools run the real Functions host locally. local.settings.json supplies app settings, including FUNCTIONS_WORKER_RUNTIME=dotnet-isolated, and Azurite emulates Azure Storage. For Durable Functions, the Durable Task Scheduler emulator runs as a container, and its dashboard is exposed on port 8082. Point the extension at it in host.json:

JSON
{
  "version": "2.0",
  "telemetryMode": "OpenTelemetry",
  "extensions": {
    "durableTask": {
      "storageProvider": {
        "type": "azureManaged",
        "connectionStringName": "DURABLE_TASK_SERVICE_CONNECTION_STRING"
      },
      "hubName": "%TASKHUB_NAME%"
    }
  }
}

This backend requires the Microsoft.Azure.Functions.Worker.Extensions.DurableTask.AzureManaged package. Locally, the connection string is Endpoint=http://localhost:8080;TaskHub=default;Authentication=None.

If your solution already uses .NET Aspire, add the function app to the AppHost with AddAzureFunctionsProject. Aspire then starts it with Azurite, wires its connections and shows its telemetry in the dashboard. For tests, treat function classes as ordinary classes. Unit test their logic with mocked services, keep orchestrators thin so that most behavior lives in testable activities, and run a few end-to-end tests against emulators.

Migrating from the In-Process Model#

Migration is mostly mechanical, and Microsoft's guide supports a slot-based cutover: switch a staging slot to the isolated model, validate it and then swap. The main changes are:

AreaIn-processIsolated worker
ProjectMicrosoft.NET.Sdk.Functions packageAzure.Functions.Sdk project SDK
Attribute[FunctionName][Function]
ExtensionsMicrosoft.Azure.WebJobs.Extensions.*Microsoft.Azure.Functions.Worker.Extensions.*
StartupFunctionsStartup classProgram.cs with FunctionsApplication.CreateBuilder
Multiple outputsIAsyncCollector<T> and out parametersCustom return type with binding attributes
Durable APIsIDurableOrchestrationContext, IDurableOrchestrationClientTaskOrchestrationContext, DurableTaskClient
Worker runtime settingdotnetdotnet-isolated

Budget time for the parts that are not mechanical: IBinder usage, host-level Application Insights configuration and any code that relied on sharing the host's assemblies.

Best Practices#

  • Target .NET 10 on the isolated worker and move to the Azure.Functions.Sdk project SDK.
  • Start new serverless apps on Flex Consumption, and size always-ready instances from real latency requirements.
  • Use identity-based connections with managed identity instead of connection strings and keys.
  • Make every handler idempotent. Triggers deliver at least once, and Durable activities can retry.
  • Settle Service Bus messages explicitly when you need dead-lettering with a reason.
  • Keep orchestrators deterministic and thin. Put all I/O in activities and use the Durable Task Scheduler for new Durable workloads.
  • Honor cancellation tokens so that scale-in and redeployments do not corrupt in-flight work.

Common Pitfalls#

  • Missing the November 10, 2026 deadline. After that date, in-process apps and apps on .NET 8 or .NET 9 run without support or security fixes.
  • Scoped services in middleware constructors. Middleware is a singleton, so capture scoped dependencies per invocation instead.
  • Non-deterministic orchestrators. Calls to DateTime.UtcNow, Guid.NewGuid(), random numbers or direct I/O break replay in subtle ways.
  • Binding configuration in custom sources. The host cannot see configuration you add in Program.cs, so triggers fail to find their connections.
  • Slow startup on Flex. Heavy initialization can exceed the 30-second host timeout and prevent the app from starting at all.
  • Assuming deployment slots on Flex. Plan releases around rolling updates or blue-green app pairs instead.

Frequently Asked Questions#

When does support for the in-process model end?#

Support for the in-process model ends on November 10, 2026. The in-process model only supports .NET 8, which also reaches end of support that day. Migrate to the isolated worker model, ideally on .NET 10 LTS, before then.

Which .NET versions can Azure Functions run?#

The isolated worker model supports .NET 10, .NET 9, .NET 8 and .NET Framework 4.8 on Functions runtime 4.x. .NET 10 apps cannot run on the Linux Consumption plan, so use Flex Consumption or another plan on Linux.

Is Flex Consumption better than the Premium plan?#

For most event-driven workloads, yes. Flex scales to zero, bills per execution and still offers VNet integration and always-ready instances. Premium remains useful for Windows apps, workloads that need deployment slots, or steady load where a warm baseline is cheaper.

Which storage backend should I use for Durable Functions?#

Use the Durable Task Scheduler for new workloads. It is the recommended, generally available backend, with a managed dashboard and a local emulator. Azure Storage still works well for small, low-cost workloads, and Netherite users should plan a move before March 31, 2028.

Can I use ASP.NET Core types in HTTP-triggered functions?#

Yes. Call ConfigureFunctionsWebApplication() and reference the Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore package, and your functions can accept HttpRequest and return IActionResult. The integration does not expose the full ASP.NET Core middleware pipeline or routing, so use Functions middleware for cross-cutting concerns.

Summary#

  • The isolated worker is the only supported .NET model after November 10, 2026. Target .NET 10 with the Azure.Functions.Sdk project SDK.
  • Program.cs gives you standard DI, options, OpenTelemetry and middleware. Bindings stay declarative, and SDK types give fine control.
  • Flex Consumption is the default serverless plan, offering per-function scaling, always-ready instances and VNet support, but no slots.
  • Durable Functions covers chaining, fan-out/fan-in, async HTTP APIs, monitors and human interaction, and the Durable Task Scheduler is the recommended backend.
  • Reduce cold starts with always-ready capacity, placeholders, ReadyToRun and lean startup code.

Further Reading#