Using Multiple Bindings with Azure Functions in Isolated Worker Process Mode
As I’ve just written about in my article about Upgrading Azure Functions to .NET 7, I’ve been working on changing some Azure Functions from .NET 6 using the “in-process” mode, to .NET 7 using the “isolated worker process” mode.
One of the big changes that I had to make was to the way I handle multiple output bindings from my functions.
Microsoft have a simple example on their Guide for running C# Azure Functions in an isolated worker process of changing to using a POCO to return more than one output binding. In my case, I needed to output two sets of data to a CosmosDB binding.
I’m going to avoid going into the details of what my data is, because it will overcomplicate it, but just for now focus on the fact that there are two separate collections that we’re reading and writing data from, and we also return an object which happens to be a bit of a mix of both those collection types. As a result of this simplification, the code looks rather convoluted, but hopefully it gets the idea across!
Here’s what I had before, as you can see I have two inputs (for two different collections), and two output bindings (for the same two collections).
[FunctionName("GetMyData")]
public async Task<ActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = "GetMyData/{partitionKey}")] HttpRequest req,
string partitionKey,
[CosmosDB(
databaseName: "MyDatabaseName",
collectionName: "MyFirstCollection",
ConnectionStringSetting = "CosmosDBConnection")]IAsyncCollector<MyFirstCollectionType> myFirstCollectionOut,
[CosmosDB(
databaseName: "MyDatabaseName",
collectionName: "MySecondCollection",
ConnectionStringSetting = "CosmosDBConnection")]IAsyncCollector<MySecondCollectionType> mySecondCollectionOut,
ILogger logger)
{
MyFirstCollectionType newItem = new MyFirstCollectionType(...);
await myFirstCollectionOut.AddAsync(newItem);
MySecondCollectionType newOtherItem = new MySecondCollectionType(...);
await mySecondCollectionOut.AddAsync(newOtherItem);
MySpecialType returnData = new MySpecialType(...); // returning one object which happens to be a merged version of the two types of data from above
return new JsonResult(returnData);
}
To migrate this to an isolated worker process, you need to return a single plain class that contains all the bindings you need, plus the actual HttpResponseData
object. Previously I was able to write to an instance of an IAsyncCollector<T>
, but now I can just add my data to a List<T>
and then return that.
Here’s what I have now:
[FunctionName("GetMyData")]
public async Task<GetMyDataOutput> Run(
[HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = "GetMyData/{partitionKey}")] HttpRequestData req,
string partitionKey,
ILogger logger)
{
List<MyFirstCollectionType> myFirstCollectionOut = new List<MyFirstCollectionType>();
List<MySecondCollectionType> mySecondCollectionOut = new List<MySecondCollectionType>();
MyFirstCollectionType newItem = new MyFirstCollectionType(...);
myFirstCollectionOut.Add(newItem);
MySecondCollectionType newOtherItem = new MySecondCollectionType(...);
mySecondCollectionOut.Add(newOtherItem);
MySpecialType returnData = new MySpecialType(...); // returning one object which happens to be a merged version of the two types of data from above
var response = req.CreateResponse(System.Net.HttpStatusCode.OK);
await response.WriteAsJsonAsync(returnData);
return new GetMyDataOutput()
{
MyFirstCollectionTypeOut = myFirstCollectionOut,
MySecondCollectionOut = mySecondCollectionOut,
HttpResponse = response
};
}
public class GetMyDataOutput
{
[CosmosDBOutput(
databaseName: "MyDatabaseName",
collectionName: "MyFirstCollection",
ConnectionStringSetting = "CosmosDBConnection")]
public IEnumerable<MyFirstCollectionType> MyFirstCollectionTypeOut { get; set; }
[CosmosDBOutput(
databaseName: "MyDatabaseName",
collectionName: "MySecondCollection",
ConnectionStringSetting = "CosmosDBConnection")]
public IEnumerable<MySecondCollectionType> MySecondCollectionOut { get; set; }
public HttpResponseData HttpResponse { get; set; }
}
Not so scary afterall, and it actually makes it a lot clearer to me, beacuse I can just look at the returned object to see all the things that are getting output.