Azure Functions Isolated Worker Model

.NET 8+ Separate Process Execution


Introduction

The Isolated Worker Model runs Azure Functions in a separate process from the Azure Functions host, providing complete isolation, independent .NET version control, and full dependency injection support. This model is the recommended approach for .NET functions in production, especially when you need greater control over the execution environment or want to use the latest .NET features.

This comprehensive guide covers:

  • Model comparison — In-process vs isolated worker
  • Project setup — Creating isolated worker projects
  • Programming model — Writing functions with HttpRequestData
  • Dependency injection — Configuring services and middleware
  • Trigger bindings — Using queues, blobs, Service Bus
  • Best practices — Production deployment guidance

Understanding the Architecture

In-Process vs Isolated Worker

┌─────────────────────────────────────────────────────────────────────┐
│                    ARCHITECTURE COMPARISON                          │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│   IN-PROCESS MODEL                                                  │
│   ───────────────────                                               │
│                                                                     │
│   ┌────────────────────────────────────────────────────────────┐    │
│   │                  AZURE FUNCTIONS HOST                      │    │
│   │                                                            │    │
│   │   ┌────────────────────────────────────────────────────┐   │    │
│   │   │              YOUR FUNCTION CODE                    │   │    │
│   │   │              (Runs in same process)                │   │    │
│   │   └────────────────────────────────────────────────────┘   │    │
│   │                                                            │    │
│   │   - .NET version = Host .NET version                       │    │
│   │   - Limited DI support                                     │    │
│   │   - Shared memory, tight coupling                          │    │
│   └────────────────────────────────────────────────────────────┘    │
│                                                                     │
│   ISOLATED WORKER MODEL                                             │
│   ──────────────────────                                            │
│                                                                     │
│   ┌─────────────────────┐     ┌─────────────────────────────────┐   │
│   │ FUNCTIONS HOST      │     │    WORKER PROCESS               │   │
│   │ (Azure runtime)     │────▶│    (.NET 8 separate process)    │   │
│   │                     │     │                                 │   │
│   │ - Triggers          │     │  ┌───────────────────────────┐  │   │
│   │ - Bindings          │     │  │   YOUR FUNCTION CODE      │  │   │
│   │ - Scaling           │     │  │   - Full DI support       │  │   │
│   │                     │     │  │   - Custom middleware     │  │   │
│   │                     │     │  │   - Independent .NET      │  │   │
│   └─────────────────────┘     │  └───────────────────────────┘  │   │
│                                │                                │   │
│                                └────────────────────────────────┘   │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

Communication Flow

┌─────────────────────────────────────────────────────────────────────┐
│                  ISOLATED WORKER COMMUNICATION                      │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│   ┌────────────────────┐                                            │
│   │   Trigger Event    │                                            │
│   │   (Queue, HTTP,    │                                            │
│   │    Timer, etc.)    │                                            │
│   └─────────┬──────────┘                                            │
│             │                                                       │
│             ▼                                                       │
│   ┌────────────────────┐       gRPC                                 │
│   │  Functions Host    │◀──────────────────┐                        │
│   │                    │                   │                        │
│   │  - Receives event  │                   │                        │
│   │  - Manages scaling │                   ▼                        │
│   │  - Handles binding │     ┌────────────────────────┐             │
│   └─────────┬──────────┘     │    Worker Process      │             │
│             │                │                        │             │ 
│             │                │  - Executes function   │             │
│             │                │  - Returns result      │             │
│             │                │  - Handled by worker   │             │
│             │                └────────────────────────┘             │
│             │                                                       │
│             ▼                                                       │
│   ┌────────────────────┐                                            │
│   │  Function Result   │                                            │
│   └────────────────────┘                                            │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

Create Isolated Worker Project

Initialize Project

# Create new isolated worker function app
func init --worker-runtime dotnet-isolated --name MyFunctionApp

# Or with .NET CLI
dotnet new worker -n MyFunctionApp -f net8.0

# Add Functions SDK
cd MyFunctionApp
dotnet add package Microsoft.Azure.Functions.Worker
dotnet add package Microsoft.Azure.Functions.Worker.Sdk

Project Structure

MyFunctionApp/
├── Program.cs
├── host.json
├── local.settings.json
├── MyFunctionApp.csproj
├── Properties/
│   └── launchSettings.json
└── Functions/
    ├── HttpFunction.cs
    ├── QueueFunction.cs
    └── TimerFunction.cs

.csproj Configuration

<Project Sdk="Microsoft.NET.Sdk.Worker">
  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
    <AzureFunctionsVersion>v4</AzureFunctionsVersion>
    <OutputType>Exe</OutputType>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Microsoft.Azure.Functions.Worker" Version="1.21.0" />
    <PackageReference Include="Microsoft.Azure.Functions.Worker.Sdk" Version="1.17.0" />
    <PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
  </ItemGroup>
  <ItemGroup>
    <None Update="host.json">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
  </ItemGroup>
</Project>

Program.cs Configuration

Basic Setup

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

var host = new HostBuilder()
    .ConfigureFunctionsWorkerDefaults()
    .ConfigureServices(services =>
    {
        // Add your services here
        services.AddSingleton<IMyService, MyService>();
        services.AddHttpClient();
    })
    .ConfigureLogging(logging =>
    {
        logging.AddConsole();
    })
    .Build();

await host.RunAsync();

Advanced Configuration

var host = new HostBuilder()
    .ConfigureFunctionsWorkerDefaults(builder =>
    {
        // Add middleware
        builder.UseMiddleware<LoggingMiddleware>();
        builder.UseMiddleware<ExceptionHandlingMiddleware>();
    })
    .ConfigureServices((context, services) =>
    {
        // Configuration
        services.AddOptions<MyOptions>()
            .Configure(options => 
            {
                options.Setting = context.Configuration["MySetting"];
            });

        // Database context
        services.AddDbContext<AppDbContext>(options =>
            options.UseSqlServer(context.Configuration.GetConnectionString("Default")));

        // Business services
        services.AddScoped<IOrderService, OrderService>();
        services.AddSingleton<ICacheService, MemoryCacheService>();

        // HTTP clients
        services.AddHttpClient<IExternalApiClient, ExternalApiClient>(client =>
        {
            client.BaseAddress = new Uri("https://api.example.com");
        });

        // Mediator for CQRS
        services.AddMediatR(cfg => 
            cfg.RegisterServicesFromAssembly(typeof(Program).Assembly));
    })
    .Build();

await host.RunAsync();

Write Functions

HTTP Trigger Function

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

public class HttpFunction
{
    private readonly IOrderService _orderService;
    private readonly ILogger<HttpFunction> _logger;

    public HttpFunction(IOrderService orderService, ILogger<HttpFunction> logger)
    {
        _orderService = orderService;
        _logger = logger;
    }

    [Function("HttpTrigger")]
    public async Task<HttpResponseData> Run(
        [HttpTrigger(AuthorizationLevel.Function, "get", "post")] HttpRequestData req)
    {
        _logger.LogInformation("HTTP trigger processed a request");

        var response = req.CreateResponse(HttpStatusCode.OK);
        
        switch (req.Method)
        {
            case "GET":
                var orders = await _orderService.GetOrdersAsync();
                await response.WriteAsJsonAsync(orders);
                break;
                
            case "POST":
                var requestBody = await req.ReadAsStringAsync();
                var order = await _orderService.CreateOrderAsync(requestBody);
                response = req.CreateResponse(HttpStatusCode.Created);
                await response.WriteAsJsonAsync(order);
                break;
                
            default:
                response = req.CreateResponse(HttpStatusCode.MethodNotAllowed);
                break;
        }
        
        return response;
    }
}

Queue Trigger Function

using Microsoft.Azure.Functions.Worker;
using Microsoft.Extensions.Logging;

public class QueueFunction
{
    private readonly IOrderProcessor _orderProcessor;
    private readonly ILogger<QueueFunction> _logger;

    public QueueFunction(IOrderProcessor orderProcessor, ILogger<QueueFunction> logger)
    {
        _orderProcessor = orderProcessor;
        _logger = logger;
    }

    [Function("ProcessOrder")]
    public async Task Run(
        [QueueTrigger("orders-queue", Connection = "StorageConnection")] string message)
    {
        _logger.LogInformation("Processing queue message: {Message}", message);

        try
        {
            var order = JsonSerializer.Deserialize<Order>(message, new JsonSerializerOptions
            {
                PropertyNameCaseInsensitive = true
            });
            
            await _orderProcessor.ProcessOrderAsync(order);
            
            _logger.LogInformation("Order {OrderId} processed successfully", order?.OrderId);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Failed to process order");
            throw;
        }
    }
}

Service Bus Trigger Function

using Microsoft.Azure.Functions.Worker;
using Azure.Messaging.ServiceBus;

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

    public ServiceBusFunction(ILogger<ServiceBusFunction> logger)
    {
        _logger = logger;
    }

    [Function("ServiceBusTrigger")]
    public async Task Run(
        [ServiceBusTrigger("orders-queue", Connection = "ServiceBusConnection")] 
        ServiceBusReceivedMessage message)
    {
        _logger.LogInformation("Service Bus message received: {MessageId}", message.MessageId);

        var body = message.Body.ToString();
        _logger.LogInformation("Message body: {Body}", body);

        // Process the message
        // ...

        // Message is auto-completed by default
    }
}

Timer Trigger Function

using Microsoft.Azure.Functions.Worker;

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

    public TimerFunction(ILogger<TimerFunction> logger)
    {
        _logger = logger;
    }

    [Function("ScheduledTask")]
    public async Task Run(
        [TimerTrigger("0 0 * * * *")] TimerInfo timerInfo)
    {
        _logger.LogInformation("Timer executed at: {Time}", DateTime.UtcNow);
        _logger.LogInformation("Next timer run: {NextRun}", timerInfo.ScheduleStatus.Next);

        // Perform scheduled task
    }
}

Dependency Injection

Register Services

// Program.cs
var host = new HostBuilder()
    .ConfigureServices((context, services) =>
    {
        // Singleton - one instance for entire app lifetime
        services.AddSingleton<ISingletonService, SingletonService>();

        // Scoped - one instance per request/session
        services.AddScoped<IScopedService, ScopedService>();

        // Transient - new instance each time
        services.AddTransient<ITransientService, TransientService>();

        // Database context
        services.AddDbContext<AppDbContext>(options =>
            options.UseSqlServer(context.Configuration.GetConnectionString("Default")));

        // HttpClient with delegation
        services.AddHttpClient<IApiClient, ApiClient>()
            .ConfigureHttpClient(client =>
            {
                client.BaseAddress = new Uri("https://api.example.com");
            })
            .AddHttpMessageHandler<CustomHandler>();

        // Options pattern
        services.Configure<FeatureFlags>(context.Configuration.GetSection("FeatureFlags"));
    })
    .Build();

// Use in functions
public class MyFunction
{
    public MyFunction(IScopedService scopedService, ISingletonService singletonService)
    {
        // Services injected via constructor
    }
}

Middleware

// Custom middleware
public class CustomMiddleware : IFunctionsWorkerMiddleware
{
    public async Task Invoke(
        FunctionContext context,
        FunctionExecutionDelegate next)
    {
        var logger = context.GetLogger<CustomMiddleware>();
        
        logger.LogInformation("Before function execution");
        
        await next(context);
        
        logger.LogInformation("After function execution");
    }
}

// Register middleware
var host = new HostBuilder()
    .ConfigureFunctionsWorkerDefaults(builder =>
    {
        builder.UseMiddleware<CustomMiddleware>();
    })
    .Build();

Binding Configuration

Input/Output Bindings

public class BlobFunction
{
    [Function("ProcessBlob")]
    public async Task Run(
        [BlobTrigger("input/{name}", Connection = "StorageConnection")] BlobClient inputBlob,
        [Blob("output/{name}", FileAccess.Write, Connection = "StorageConnection")] BlobClient outputBlob,
        string name)
    {
        // Read from input blob
        var downloadResponse = await inputBlob.DownloadContentAsync();
        var content = downloadResponse.Value.Content.ToString();

        // Process and write to output blob
        var processed = ProcessContent(content);
        await outputBlob.UploadAsync(BinaryData.FromString(processed));
    }
}

Cosmos DB Binding

public class CosmosFunction
{
    [Function("GetDocument")]
    public async Task<HttpResponseData> Run(
        [HttpTrigger(AuthorizationLevel.Function, "get")] HttpRequestData req,
        [CosmosDB("database", "collection", 
            Id = "{id}", 
            PartitionKey = "{partition}")] 
        MyDocument document)
    {
        var response = req.CreateResponse(document != null 
            ? HttpStatusCode.OK 
            : HttpStatusCode.NotFound);
        
        await response.WriteAsJsonAsync(document);
        return response;
    }

    [Function("CreateDocument")]
    [return: CosmosDB("database", "collection")]
    public MyDocument Create(
        [HttpTrigger(AuthorizationLevel.Function, "post")] HttpRequestData req)
    {
        // Create and return document
        return new MyDocument { Id = Guid.NewGuid().ToString() };
    }
}

Best Practices

Configuration Checklist

PracticeDescription
Use DI for all servicesDon't instantiate services manually
Register at appropriate lifetimeSingleton, Scoped, or Transient
Use ILogger<T>Get typed logger for better filtering
Handle exceptionsCatch and log errors properly
Configure retry policyFor queue/Service Bus triggers

Logging

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

    public FunctionWithLogging(ILogger<FunctionWithLogging> logger)
    {
        _logger = logger;
    }

    [Function("Process")]
    public async Task Run([QueueTrigger("queue")] string message)
    {
        _logger.LogInformation("Processing message: {MessageLength}", message?.Length);
        
        try
        {
            // Process message
            _logger.LogInformation("Message processed successfully");
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error processing message");
            throw;
        }
    }
}

Related Topics


Azure Integration Hub - Intermediate Level