.NET Aspire Integration with Azure Functions

.NET Aspire( is one of the major additions to the .NET ecosystem since the release of .NET core itself. .NET Aspire provides the packages and the tools required for dev-time orchestration of not only applications but also services such as databases or storage system.

One of the newest - and the nicest - additions to .NET Aspire is the ability to include Azure Functions to the orchestration and access the Azure function in the dashboard as any other resource.

Architecture

The solution relies on a minimal architecture where a client application written using Blazor Server publishes a queue message (a serialized email) that the Azure Function will pick and send (sending is just a log message here).

Making it Work

The Client

Configuring the Queue

The host builder should be configured to provide the QueueServiceClient to the consuming pages and/or services. For this, in the Program.cs, we should do the following:

builder.AddAzureQueueClient("Queues")

This means that the QueueServiceClient is added to DI. However, we will have a problem: when publishing to the queue, the azure function will not be able to read the message because it expects the messages to be base64 encoded.

To fix this problem, we will change the queue bootstrapping as follows:

builder.AddAzureQueueClient("Queues", configureSettings: null, configureClientBuilder: config =>
{
    config.ConfigureOptions(configureOptions =>
    {
        configureOptions.MessageEncoding = QueueMessageEncoding.Base64;
    });
});

Using the Queue

Using the queue, is straightforward. First, we have to inject the QueueServiceClient as follows:

@inject QueueServiceClient Queues

and then use the queue to send the message:

var queue = Queues.GetQueueClient("emails");
await queue.CreateIfNotExistsAsync();
var message = JsonSerializer.Serialize(_emailMessage);
await queue.SendMessageAsync(message);

The Function

Nothing fancy about the azure fuction. It will be a standard function with a queue trigger consuming the message:

[Function(nameof(EmailFunction))]
public void Run([QueueTrigger("emails")] EmailMessage message)
{
        logger.LogInformation("Sending an email to {To} with body {Body}", message.To, message.Body);
}

The only different thing, is that we will use the .NET Aspire service defaults as follows:

var builder = FunctionsApplication.CreateBuilder(args);

builder.AddServiceDefaults();

The Host

The host should setup the following components:

  • The storage
  • The function app
  • The client app

The Storage

Azure storage integration is explained in details here. From the host point of view, storage is just a resource as any other resource.

The following code sets the storage up for the orchestration:

var builder = DistributedApplication.CreateBuilder(args);

var storage = builder.AddAzureStorage("storage")
                     .RunAsEmulator();

var queues = storage.AddQueues("Queues");

The AddAzureStorage method adds the storage service to the orhechestration. This call: storage.AddQueues("Queues") means that we add a queue service that will provide the connection string through the key Queues.

The .RunAsEmulator() uses the emulated version of azure storage instead of a concrete storage account. Of course, this code should not be used in production.

The Function

Now, the host should add the azure function as a resource. Each resource can have a dependency, in our case, the function should wait for the queue to be running and use the storage for function storage.

builder
    .AddAzureFunctionsProject<EmailPublisherFunction>("EmailFunction")
    .WithExternalHttpEndpoints()
    .WithHostStorage(storage)
    .WaitFor(queues);

The Client

The client is the last resource that we will start. It has a dependency on the queue.

builder
    .AddProject<Projects.AspireFunctionsClient>("EmailClient")
    .WithReference(queues)
    .WaitFor(queues);

Execution

We have now everything we need to make it work. Let's see everything in action.

The Aspire Dashboard

The resource list is displayed as follows:

We can see clearly the three resources that we are using (client, function, and storage + queues).

The 9.2 of Aspire has a very nice addition which is the resource graph, an awesome way to illustrate the dependencies:

Aspire uses Docker containers to run certain resources such as the Azure emulator. If you run docker ps in your terminal, you can see the Azure emulator container running:

Running App

You can test it all together. The - very - ugly Blazor app just takes an email and a body and sends them to the queue.

You can see in the function logs that it actually works!

The Code

The sample code can be found here: https://github.com/nubiquest-blogs/aspire-functions

  • Azure
  • Azure Functions
  • Aspire

We use cookies to improve your experience. By continuing to browse, you agree to our Privacy Policy