Dynamic Binding in Azure Functions with Imperative Runtime Bindings

When creating precompiled Azure Functions, bindings (such as a blob output bindings) can be declared in the function code, for example the following code defines a blob output binding:

[Blob("todo/{rand-guid}")]

This binding creates a new blob with a random (GUID) name. This style of binding is called declarative binding, the binding details are declared as part of the binding attribute.

In addition to declarative binding, Azure Functions also offers imperative binding. With this style of binding, the details of the binding can be chosen at runtime. These details could be derived from the incoming function trigger data or from an external place such as a configuration value or database item

To create imperative bindings, rather than using a specific binding attribute, a parameter of type IBinder is used. At runtime, a binding can be created (such as a blob binding, queue binding, etc.) using this IBinder. The Bind<T> method of the IBinder can be used with T representing an input/output type that is supported by the binding you intend to use.

The following code shows imperative binding in action. In this example blobs are created and the blob path is derived from the incoming JSON data, namely the category.

public static class CreateToDoItem
{
    [FunctionName("CreateToDoItem")]
    public static async Task<HttpResponseMessage> Run(
        [HttpTrigger(AuthorizationLevel.Function, "post", Route = null)]HttpRequestMessage req,
        IBinder binder,
        TraceWriter log)
    {
        ToDoItem item = await req.Content.ReadAsAsync<ToDoItem>();
        item.Id = Guid.NewGuid().ToString();

        BlobAttribute dynamicBlobBinding = new BlobAttribute(blobPath: $"todo/{item.Category}/{item.Id}");

        using (var writer = binder.Bind<TextWriter>(dynamicBlobBinding))
        {
            writer.Write(JsonConvert.SerializeObject(item));
        }

        return req.CreateResponse(HttpStatusCode.OK, "Added " + item.Description);
    }
}

If the following 2 POSTS are made:

{
    "Description" : "Lift weights",
    "Category" : "Gym"
}
{
    "Description" : "Feed the dog",
    "Category" : "Home"
}

Then 2 blobs will be output with the following paths - note the random filenames and imperatively-bound paths: Gym and Home :

http://127.0.0.1:10000/devstoreaccount1/todo/Gym/5dc4eb72-0ae6-42fc-9a8b-f4bf646dcd28

http://127.0.0.1:10000/devstoreaccount1/todo/Home/530373ef-02bc-4200-a4e7-948448ac081b

Comments (4) -

  • James Webster

    3/7/2018 11:24:13 PM | Reply

    Probably worth making clear that this applies to output bindings only... unless you have an example where input bindings can be established imperatively? It couldn't happen inside the function itself, but outside in some sort of configuration code.

    • Jason Roberts

      3/9/2018 5:48:24 AM | Reply

      Thanks James, you can read an input blob based on the filename contained in a message if using a [QueueTrigger] by using the special binding {queueTrigger} - e.g.

      public static void Run(
                  [QueueTrigger("remove-category", Connection = "")]string inputBlobPath,
                  [Blob("queueTrigger}", FileAccess.Read)] Stream blobItemToRead,
                  TraceWriter log)

  • Frank Monroe

    3/18/2018 9:35:15 PM | Reply

    Is this the right technique to use for CosmosDB multi-tenant use case; eg: I want to input/output bind my Azure function to different ComosDB databases and collections at run time depending on data in the request (for example the UserID). It seems to me this is a frequent use case for multi-tenant and I have not been able to find much information about best practices for this use case (nor any practical example). Can this be done? Thank you.

  • Seema Mehta

    5/3/2018 5:46:33 AM | Reply

    Is there a way to update bindings of Cosmos DB triggered functions at run time?  
    Otherwise how can a Cosmos DB triggered function handle regeneration of Cosmos DB keys without a disruption / restart?
    Thanks

Add comment

Loading