The .NET Isolated Worker model for Azure Functions is a game-changer because it decouples your function’s .NET runtime from the Functions Host, giving you control over dependencies and runtime versions.
Let’s see it in action with a simple HTTP-triggered function.
First, create a new Azure Functions project using the .NET Isolated Worker model. You’ll use the Azure Functions Core Tools for this:
func init MyIsolatedProject --dotnet-isolated
cd MyIsolatedProject
func new --name HttpTriggerFunction --template "HTTP trigger"
This creates a MyIsolatedProject directory with a MyIsolatedProject.csproj file and a HttpTriggerFunction.cs file. The .csproj file will look something like this, notice the AzureFunctionsVersion set to v4 and the Microsoft.NET.Sdk.Functions SDK:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<AzureFunctionsVersion>v4</AzureFunctionsVersion>
<OutputType>Exe</OutputType>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Http" Version="3.1.0" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.Sdk" Version="1.17.0" />
<PackageReference Include="Microsoft.Azure.Functions.Worker" Version="1.22.0" />
</ItemGroup>
<ItemGroup>
<None Update="host.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="local.settings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<CopyToPublishDirectory>Never</CopyToPublishDirectory>
</None>
</ItemGroup>
<ItemGroup>
<Using Include="Microsoft.Extensions.DependencyInjection" />
</ItemGroup>
</Project>
The Program.cs file is where the magic happens for dependency injection and host configuration:
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
var host = new HostBuilder()
.ConfigureFunctionsWorkerDefaults()
.ConfigureServices(services =>
{
services.AddSingleton<IHttpResponderService, HttpResponderService>(); // Example of adding a service
})
.Build();
host.Run();
Here, ConfigureFunctionsWorkerDefaults() sets up the necessary infrastructure for the isolated worker. You can then use standard .NET dependency injection patterns in ConfigureServices.
The HttpTriggerFunction.cs file contains your actual function logic. Notice it takes an HttpRequestData and returns an HttpResponseData:
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Http;
using Microsoft.Extensions.Logging;
using System.Net;
namespace MyIsolatedProject;
public class HttpTriggerFunction
{
private readonly ILogger _logger;
private readonly IHttpResponderService _httpResponderService; // Injected service
public HttpTriggerFunction(ILoggerFactory loggerFactory, IHttpResponderService httpResponderService)
{
_logger = loggerFactory.CreateLogger<HttpTriggerFunction>();
_httpResponderService = httpResponderService;
}
[Function("HttpTriggerFunction")]
public async Task<HttpResponseData> Run(
[HttpTrigger(AuthorizationLevel.Function, "get", "post")] HttpRequestData req)
{
_logger.LogInformation("C# HTTP trigger function processed a request.");
var response = req.CreateResponse(HttpStatusCode.OK);
response.Headers.Add("Content-Type", "text/plain; charset=utf-8");
// Using the injected service
string greeting = _httpResponderService.CreateGreeting(req.Query["name"]);
response.WriteString($"Hello, {greeting}");
return response;
}
}
// Example interface and implementation for dependency injection
public interface IHttpResponderService
{
string CreateGreeting(string name);
}
public class HttpResponderService : IHttpResponderService
{
public string CreateGreeting(string name)
{
return string.IsNullOrEmpty(name) ? "World" : name;
}
}
To run this locally, you’ll need the Azure Functions Core Tools installed. Navigate to your project directory in the terminal and run:
func start
You’ll see output indicating the function host has started, and it will provide a local URL (e.g., http://localhost:7071/api/HttpTriggerFunction) that you can use to test your function. Making a GET request to http://localhost:7071/api/HttpTriggerFunction?name=Alice will return "Hello, Alice".
The key benefit here is the complete independence of your .NET runtime. You can target net8.0, net7.0, or even net6.0 without being tied to the version of .NET the Functions host itself is running on. This allows for easier migration, finer control over dependencies, and the use of newer language features without waiting for host updates. You can also have multiple functions in the same project targeting different .NET versions if needed, although this is less common.
The HttpRequestData and HttpResponseData types are specific to the isolated worker model. They provide a more direct representation of the HTTP request and response, abstracting away some of the complexities of the in-process model and offering a cleaner API for common tasks like creating responses and accessing headers.
When you publish this function to Azure, the Azure Functions runtime will provision an environment that hosts your isolated .NET worker process. The host.json file allows you to configure host-level settings, like logging levels or extension configurations, and local.settings.json is for local development environment variables and connection strings.
The ConfigureFunctionsWorkerDefaults() method in Program.cs is quite powerful. It registers essential services and configures the middleware pipeline for the Functions worker. You can chain other configuration methods onto HostBuilder to further customize your application, such as adding logging providers or other host-specific settings.
Under the hood, the Functions host and your isolated worker communicate via gRPC. This allows them to be separate processes and different runtimes, providing the isolation that gives this model its name. The host manages the lifecycle of your functions and routes requests to your worker process.
A common pattern you’ll encounter is managing middleware for request processing. You can add custom middleware to the pipeline within ConfigureFunctionsWorkerDefaults to intercept requests before they reach your function handler, enabling cross-cutting concerns like authentication, request validation, or custom logging.
The ability to control the .NET runtime version and its dependencies independently of the Azure Functions host is the most significant advantage of the Isolated Worker model. This isolation means you can upgrade your .NET version or introduce new NuGet packages without worrying about compatibility issues with the Functions host itself, which often lags behind the latest .NET releases.
The next step in exploring Azure Functions with .NET Isolated Worker is to understand how to manage different trigger types and integrate with other Azure services like Cosmos DB or Service Bus.