Isolated Worker in .NET 9: wat verandert er en waarom het je CI/CD pipeline raakt
Een praktische blik op het Isolated Worker-model in .NET 9 Azure Functions, de gevolgen voor lokale ontwikkeling en hoe je je GitHub Actions workflow hierop aanpast.
Van in-process naar Isolated Worker
Tot en met .NET 6 draaide de meeste Azure Functions code in-process samen met de Functions host. Handig, maar ook beperkend: je zat vast aan de dependency-versies die de host meebracht, en upgraden naar een nieuwe .NET-versie betekende wachten op Microsoft.
Met het Isolated Worker-model draait je function-code in een apart proces. De host is nog steeds verantwoordelijk voor het ontvangen van triggers en bindings, maar communiceert via gRPC met jouw worker-proces. Dat geeft je volledige controle over de runtime en dependencies.
In .NET 9 is het in-process model definitief uitgefaseerd. Als je nog in-process draait, is dit hét moment om te migreren.
Wat er praktisch verandert
De grootste zichtbare wijziging is in je Program.cs. Waar je vroeger een lege startup had (of helemaal geen), heb je nu een expliciete host builder:
var host = new HostBuilder()
.ConfigureFunctionsWorkerDefaults()
.ConfigureServices(services =>
{
services.AddApplicationInsightsTelemetryWorkerService();
services.ConfigureFunctionsApplicationInsights();
})
.Build();
await host.RunAsync();
Middleware werkt nu als een echte pipeline — vergelijkbaar met ASP.NET Core. Je kunt request-afhandeling onderscheppen, foutafhandeling centraliseren en authenticatie generiek regelen:
.ConfigureFunctionsWorkerDefaults(worker =>
{
worker.UseMiddleware<ExceptionHandlingMiddleware>();
})
Binding-attributen zijn grotendeels hetzelfde, maar het return-type van je functions verschilt. Je retourneert nu een HttpResponseData in plaats van een IActionResult:
[Function("Contact")]
public async Task<HttpResponseData> Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "post")] HttpRequestData req)
{
var response = req.CreateResponse(HttpStatusCode.OK);
await response.WriteAsJsonAsync(new { success = true });
return response;
}
Impact op je CI/CD pipeline
De switch naar Isolated Worker heeft directe gevolgen voor hoe je build en deploy pipeline eruitziet.
Build-stap: je gebruikt nog steeds dotnet publish, maar zorg dat je target framework klopt. Voor .NET 9 Isolated:
<TargetFramework>net9.0</TargetFramework>
<AzureFunctionsVersion>v4</AzureFunctionsVersion>
GitHub Actions workflow: de azure/functions-action werkt prima met Isolated Worker, maar let op de package parameter — die moet naar de gepubliceerde output wijzen, niet naar de source directory:
- name: Deploy Azure Functions
uses: azure/functions-action@v1
with:
app-name: ${{ vars.FUNCTION_APP_NAME }}
package: ./api/publish
publish-profile: ${{ secrets.AZURE_FUNCTIONAPP_PUBLISH_PROFILE }}
Lokale ontwikkeling: func start werkt nog steeds, maar je moet eerst zelf builden. Voeg een watch-stap toe in je dev-workflow:
dotnet watch --project api/ build &
func start --csharp
Of gebruik de Tasks in .vscode/tasks.json om dit te automatiseren.
Testbaarheid
Een onderschat voordeel van Isolated Worker is de verbeterde testbaarheid. Omdat je functions gewone klassen zijn met constructor-injectie, kun je ze eenvoudig unit-testen zonder de Functions host te hoeven opstarten.
Met xUnit en FluentAssertions:
[Fact]
public async Task Contact_ValidPayload_Returns200()
{
// Arrange
var function = new ContactFunction(_mockMailService.Object, _mockLogger.Object);
var req = TestHttpRequestFactory.CreatePostRequest(validPayload);
// Act
var response = await function.Run(req);
// Assert
response.StatusCode.Should().Be(HttpStatusCode.OK);
}
De migratie kost een middag, maar je wint er structureel betere testbaarheid, toekomstbestendigheid en een schonere pipeline voor terug.