I have a function app that we are upgrading from the In-Process Model to the Isolated Worker Model. Below I have a dumbed-down, pseudocode version of what I am working on with the relevant method signature and call.
[FunctionName("DummyFunctionName")]
public async Task RunDummyFunction([TimerTrigger("%DummyTrigger%")] TimerInfo timerInfo,[ServiceBus("%DummyQueue%", EntityType = ServiceBusEntityType.Queue, Connection = "DummyConnectionString")] IAsyncCollector<string> queue)
{
await _manager.DoStuff(queue);
}
We are passing the IAsyncCollector to the _manager.DoStuff. In that method, it will eventually call await queue.AddAsync(n1) where n1 is an IEnumerable string.
With the upgrades to the isolated worker model, I am unable get that IAsyncCollector anymore and pass it to the function.
I know I can decorate an object with the ServiceBus and return the object, but that is not going to return a Task in _manager.DoStuff. For example, we cannot do what it says in this article: https://weblogs.asp.net/sfeldman/functions-isolated-worker-sending-multiple-messages because it doesn't return a Task.
This might be helpful too: https://learn.microsoft.com/en-us/azure/azure-functions/functions-bindings-service-bus-output?tabs=python-v2%2Cisolated-process%2Cnodejs-v4%2Cextensionv5&pivots=programming-language-csharp#example
Please advise! Thank you!
(Following on from my comment)
Seeing as you already have a Service class, I'd be tempted to ditch the Function bindings entirely and instead use a Repository pattery. So, make the service take a repository interface representing the queue output, and then inject an implementation based on Service Bus. Here's how.
Ignore that it's Service Bus for now, and just create your own interface that represents a queue. Maybe put the interface in the same project as your Service.
namespace DummyApp.Services;
public interface IQueue
{
Task Enqueue(string toQueue);
}
(this will be useful if you want to Unit Test your Manager class too, as we'll see later.)
IQueue
abstractionYour Manager class now takes an IQueue
object in the Constructor, and the DoStuff
method no longer requires any parameters:
namespace DummyApp.Services;
public class Manager(IQueue _queue)
{
public async Task DoStuff()
{
string[] thingsToEnqueue = ["this", "that"]; // Or wherever they come from.
foreach (var thing in thingsToEnqueue)
{
// Call the IQueue method for each thing you send.
// (NB if you want to do this as a batch, I'll show that later)
await _queue.Enqueue(thing);
}
}
}
IQueue
implementation that uses Service BusThis takes a Constructor parameter of ServiceBusClient
(which we will wire-up in the Dependency Injection config later.)
using Azure.Messaging.ServiceBus;
using DummyApp.Services;
namespace DummyApp.ServiceBus;
public class ServiceBusQueue(ServiceBusClient _client) : IQueue
{
public async Task Enqueue(string toQueue)
{
var sender = _client.CreateSender("DummyQueue");
var message = new ServiceBusMessage(toQueue);
await sender.SendMessageAsync(message);
}
}
Obviously you'll want to pass through any config etc for the queue names.
Note, you might want to create a new Project to put this implementation in, so that you can keep the Service Bus dependencies separate from your Services or your Function App.
This is for a .NET 8 Isolated Function using the latest (at time of recording...) ASP.NET Core style of Function App. Merge this with any other Dependency Injection config that you need to do.
using Microsoft.Extensions.Azure;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using DummyApp.ServiceBus;
using DummyApp.Services;
var host = new HostBuilder()
.ConfigureFunctionsWebApplication()
.ConfigureServices(services =>
{
services.AddAzureClients(configureClients =>
{
configureClients.AddServiceBusClient("DummyConnectionString");
});
services.AddScoped<IQueue, ServiceBusQueue>();
services.AddScoped<Manager>();
})
.Build();
host.Run();
Because your Manager class doesn't need any Service Bus (or Azure Functions) specific types, you can Unit Test the behaviour by just mocking the IQueue
implementation with something else.
If you want to send all your items at once, you can just modify or extend the IQueue
implementation accordingly. For example:
public interface IQueue
{
Task EnqueueAsBatch(string[] toQueue);
}
and:
public class ServiceBusQueue(ServiceBusClient _client) : IQueue
{
public async Task EnqueueAsBatch(string[] toQueue)
{
var sender = _client.CreateSender("DummyQueue");
var batch = await sender.CreateMessageBatchAsync();
foreach (string item in toQueue)
{
batch.TryAddMessage(new ServiceBusMessage(item));
}
await sender.SendMessagesAsync(batch);
}
}
and change your Manager class accordingly:
public class Manager(IQueue _queue)
{
public async Task DoStuff()
{
string[] thingsToEnqueue = ["this", "that"];
await _queue.EnqueueAsBatch(thingsToEnqueue);
}
}
Hope that's helpful!