5

Out bindings has example:

ICollector<T> (to output multiple blobs)

And also:

Path must contain the container name and the blob name to write to. For example,
if you have a queue trigger in your function, you can use "path":
"samples-workitems/{queueTrigger}" to point to a blob in the samples-workitems
container with a name that matches the blob name specified in the trigger
message.

And default in "Integrate" UI has default as:

Path: outcontainer/{rand-guid}

But this isn't sufficient for me to make headway. If I'm coding in C#, what is the syntax for function.json and for run.csx to output multiple blobs to a container?

Chris Harrington
  • 1,238
  • 2
  • 15
  • 28

1 Answers1

3

There are several different ways you can accomplish this. First, if the number of blobs you need to output is fixed, you can just use multiple output bindings.

using System;

public class Input
{
    public string Container { get; set; }
    public string First { get; set; }
    public string Second { get; set; }
}

public static void Run(Input input, out string first, out string second, TraceWriter log)
{
    log.Info($"Writing 2 blobs to container {input.Container}");
    first = "Azure";
    second = "Functions";
}

And the corresponding function.json:

{
  "bindings": [
    {
      "type": "manualTrigger",
      "direction": "in",
      "name": "input"
    },
    {
      "type": "blob",
      "name": "first",
      "path": "{Container}/{First}",
      "connection": "functionfun_STORAGE",
      "direction": "out"
    },
    {
      "type": "blob",
      "name": "second",
      "path": "{Container}/{Second}",
      "connection": "functionfun_STORAGE",
      "direction": "out"
    }
  ]
}

To test the above, I send a test JSON payload to the function, and the blobs are generated:

{
  Container: "test",
  First: "test1",
  Second: "test2"
}

The sample above demonstrates how the blob container/name values can be bound from the input (via the {Container}/{First} {Container}/{Second} path expressions). You just have to define a POCO capturing the values you want to bind to. I used ManualTrigger here for simplicity, but this works for the other trigger types as well. Also, while I chose to bind to out string Types, you can bind to any of the other supported types: TextWriter, Stream, CloudBlockBlob, etc.

If the number of blobs you need to output is variable, then you can use Binder to imperatively bind and write blobs in your function code. See here for more details. To bind to multiple outputs, you'd just perform multiple imperative bindings using that technique.

FYI: our documentation was incorrect, so I logged a bug here to get that fixed :)

Community
  • 1
  • 1
mathewc
  • 13,312
  • 2
  • 45
  • 53
  • I am trying that the imperative pattern. Am getting a compile error - see below. – Chris Harrington Feb 12 '17 at 00:47
  • // imperative binding to storage blobPath = $"json/{fileNamePart}_{dateString}"; var attributes = new Attribute[] { new BlobAttribute(blobPath), new StorageAccountAttribute(storageAccount) }; using (var writer = binder.Bind(attributes)) { writer.Write(jsonString.ToString()); } – Chris Harrington Feb 12 '17 at 00:47
  • The error: 2017-02-12T00:43:43.243 Function started (Id=6d48c79d-5af3-4c9a-b4ab-242d186e7c33) 2017-02-12T00:43:43.243 Function compilation error 2017-02-12T00:43:43.243 (225,73): error CS1026: ) expected 2017-02-12T00:43:43.243 (225,61): error CS1503: Argument 2: cannot convert from 'System.Attribute[]' to 'System.Attribute' 2017-02-12T00:43:43.243 (161,13): warning CS0162: Unreachable code detected 2017-02-12T00:43:43.243 (191,17): warning CS0162: Unreachable code detected 2017-02-12T00:43:43.243 Function completed (Failure, Id=6d48c79d-5af3-4c9a-b4ab-242d186e7c33) – Chris Harrington Feb 12 '17 at 00:48
  • By the way, I think that clicking "Run" should always try to compile the csx file. Currently it does not. Only when triggered do I get the above compile error. – Chris Harrington Feb 12 '17 at 00:49
  • You're calling the wrong Binder method - please see the example code again. It's BindAsync. You should change your method signature to "async Task" to use that method. – mathewc Feb 12 '17 at 01:52
  • If I use BinderAsync I get the error that I can't use async in this context. I'll get the full error message in a moment. – Chris Harrington Feb 12 '17 at 18:59
  • 2017-02-12T19:08:45.309 Function compilation error 2017-02-12T19:08:45.309 (225,67): error CS1503: Argument 2: cannot convert from 'System.Attribute[]' to 'System.Attribute' 2017-02-12T19:08:45.309 (225,37): error CS4033: The 'await' operator can only be used within an async method. Consider marking this method with the 'async' modifier and changing its return type to 'Task'. – Chris Harrington Feb 12 '17 at 19:10
  • I am using the Run method signature that the portal created for me for a Blob trigger. Can I just change it to async? What is the syntax for that for a Blob trigger? – Chris Harrington Feb 12 '17 at 19:12
  • Current signature is: public static void Run(Stream myBlob, string blobname, out string emailQueueItem, Binder binder, TraceWriter log) – Chris Harrington Feb 12 '17 at 19:45
  • Because you're using out parameters, you can't change your function to async. One option is to wait for the results via GetResult, e.g. binder.BindAsync(...).GetAwaiter().GetResult(). Another option is to change your "out string" binding to use IAsyncCollector, which would then allow your function to be async. You'd then change the signature to return "async Task". The second option would be better. – mathewc Feb 12 '17 at 21:56
  • Thanks Matthew. What is it that makes the second option better? Just async being better in general? – Chris Harrington Feb 14 '17 at 01:24
  • It's best not to block synchronously on async Tasks. If you're unblocked now on this, please mark this question as answered, thanks :) – mathewc Feb 14 '17 at 01:30
  • I'm just trying out your suggestion now. – Chris Harrington Feb 14 '17 at 01:30
  • Pursued the "Second option" and seems to be working for me – Chris Harrington Feb 14 '17 at 06:19
  • I spoke to soon. Only works for really small blobs. For larger ones, there is no error and my logging says it was posted to storage. But it never appears in Blob Storage. How would I go about diagnosing this? – Chris Harrington Feb 14 '17 at 07:37
  • The code: blobPath = $"json/{fileNamePart}_{dateString}"; var attributes = new Attribute[] { new BlobAttribute(blobPath), new StorageAccountAttribute("deal2_STORAGE") }; using (var writer = await binder.BindAsync(attributes).ConfigureAwait(false)) { writer.Write(jsonString.ToString()); } Logger.Info($"added JSON blob {blobPath}, length {jsonString.Length}"); – Chris Harrington Feb 14 '17 at 07:43
  • Bobs sized around 10K work. Second test had blobs sized around 25K and they never show up in storage. – Chris Harrington Feb 14 '17 at 07:44
  • I'll start a new question regarding the new issue above since this comment thread has grown long. – Chris Harrington Feb 14 '17 at 18:09
  • would love an example of how to configure binder to do imperative input bindings, i can only find examples that do output – Alex Gordon May 29 '19 at 02:06