You can orchestrate an Azure Function right inside .NET Aspire, complete with a local Azurite storage emulator and a single place (the Aspire dashboard) to observe everything. This post walks through a minimal setup using the isolated worker model on .NET 9.
What we’ll build
- A simple HTTP-triggered Azure Function (
/api/Hello) - An Azurite container for Storage (Blob/Queue/Table)
- An Aspire AppHost that:
- starts Azurite,
- wires the Function’s
AzureWebJobsStorage, - exposes the Function on port 7071
Create the project
# Templates
dotnet new install Microsoft.Azure.Functions.Worker.ProjectTemplates
dotnet new func -n AzureFunction -F net9.0
dotnet new install Microsoft.Azure.Functions.Worker.ItemTemplates
dotnet new http -n Hello -A Anonymous -p:n AzureFunction
# Aspire orchestration
dotnet new aspire-apphost -n AppHost
dotnet new aspire-servicedefaults -n ServiceDefaults
# (macOS) Core Tools for local runs
brew tap azure/functions
brew install azure-functions-core-tools@4
echo 'export PATH="/opt/homebrew/opt/azure-functions-core-tools@4/bin:$PATH"' >> ~/.zshrc
source ~/.zshrc
func --version
Folder layout
09_AzureFunction/
├─ AppHost/
├─ ServiceDefaults/
├─ AzureFunction.csproj
├─ Program.cs
├─ Hello.cs
├─ host.json
├─ local.settings.json
└─ README.md
Azure Function (isolated worker)
Hello.cs
using Microsoft.Azure.Functions.Worker;
using Microsoft.Extensions.Logging;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
namespace AzureFunction;
public class Hello
{
private readonly ILogger<Hello> _logger;
public Hello(ILogger<Hello> logger) => _logger = logger;
[Function("Hello")]
public IActionResult Run([HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")] HttpRequest req)
{
_logger.LogInformation("C# HTTP trigger function processed a request.");
return new OkObjectResult("Welcome to Azure Functions!");
}
}
local.settings.json (used by Core Tools; Aspire will override the storage connection)
{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
"FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated"
}
}
Aspire orchestration (AppHost)
AppHost/Program.cs
using Aspire.Hosting;
var builder = DistributedApplication.CreateBuilder(args);
// Azurite (Storage emulator)
var azurite = builder.AddContainer("azurite", "mcr.microsoft.com/azure-storage/azurite", "3.31.0")
.WithEndpoint(10000, 10000, "blob")
.WithEndpoint(10001, 10001, "queue")
.WithEndpoint(10002, 10002, "table");
// Build a Storage connection string from the allocated endpoints
string BuildAzuriteConn() =>
"DefaultEndpointsProtocol=http;" +
"AccountName=devstoreaccount1;" +
"AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;" +
$"BlobEndpoint={azurite.GetEndpoint(\"blob\").Url}devstoreaccount1;" +
$"QueueEndpoint={azurite.GetEndpoint(\"queue\").Url}devstoreaccount1;" +
$"TableEndpoint={azurite.GetEndpoint(\"table\").Url}devstoreaccount1;";
builder.AddProject<Projects.AzureFunction>("azure-func")
.WithHttpEndpoint(port: 7071, name: "http")
.WithEnvironment("AzureWebJobsStorage", BuildAzuriteConn) // computed at runtime
.WithEnvironment("FUNCTIONS_WORKER_RUNTIME", "dotnet-isolated")
.WaitFor(azurite);
builder. Build().Run();
What’s happening
- Azurite runs as a container with three endpoints (Blob/Queue/Table).
- The connection string is computed at runtime (after ports are known) and injected into the Function via env var.
- The Function is exposed on http://localhost:7071; Aspire dashboard will show health/logs.
Run it
From the solution root:
dotnet run --project AppHost
Then open:
- Function:
http://localhost:7071/api/Hello - (If Core Tools UI shows a different port, Aspire’s mapping still forwards 7071 to the function.)
You should see:
Welcome to Azure Functions!
Notes & tips
- Core Tools are required to run the Function locally; Aspire handles orchestration, not the host runtime itself.
- If 7071 is busy, change
.WithHttpEndpoint(port: …)in AppHost. - The Azurite key here is the standard dev key—safe for local only.
- You can add Bindings (Queues/Blobs) and they’ll use the same
AzureWebJobsStorageprovided by Aspire.
Troubleshooting
- “Host not running” / 500s
Confirm Core Tools v4 is on PATH (func --version), and your Function targets .NET 9 isolated. - Storage connection errors
Ensure Azurite ports (10000–10002) are free. On macOS,lsof -i :10000. - CORS in frontends
Add"extensions": { "http": { "cors": { "allowedOrigins": [ "https://localhost:xxxx" ] } } }tohost.jsonfor local FE calls.
Next steps
- Add a QueueTrigger or BlobTrigger function—no code changes in AppHost required.
- Wire a Web or API project that calls the Function, all inside Aspire.
- Swap Azurite for a real Azure Storage connection via Aspire user secrets or environment overrides.
That’s it—Azure Functions running locally with Azurite, orchestrated by .NET Aspire for a clean, one-command dev loop.
That’s all folks!
Cheers!
Gašper Rupnik
{End.}

Leave a comment