Using Blobs in Azure Functions with F#
Azure Functions supports both writing functions in F#, and binding to Azure blobs. There is documentation for binding to blobs as well as using F# with Azure Functions, but there are a few gotchas, so here’s a quick overview of what you can do.
We can set up our F# Azure function to be triggered by a blob quite easily by selecting the built-in blob trigger template:
This creates a basic function for you with the bindings all set up:
open System
let Run(myBlob: Stream, name: string, log: TraceWriter) =
log.Verbose(sprintf "F# Blob trigger function processed blob\n Name: %s \n Size: %d Bytes" name myBlob.Length)
As you can see, the myBlob
parameter is a Stream
, but there are several supported types. You can use string
if the blob has text contents. Also, you can ask for an ICloudBlob
if you want direct access to things like the blob Metadata, which you can do like this:
#r "Microsoft.WindowsAzure.Storage"
open System
open Microsoft.WindowsAzure.Storage
open Microsoft.WindowsAzure.Storage.Blob
let Run(myBlob: ICloudBlob, name: string, log: TraceWriter) =
log.Verbose(sprintf "F# Blob trigger function processed blob\n Name: %s \n Size: %d Bytes" name myBlob.Properties.Length)
According to the docs we should also be able to define a CLI mutable type and bind to that as well, if our blob contains JSON. However, I have not been able to get this to work, and it appears to be a limitation with the blob bindings in Azure Functions at the moment.
If you’re wondering what the name
parameter is, this comes from the path setting of the trigger:
This means that we can use that name when we define our output bindings. You can also use patterns like {filename}.{ext}
and even create a random filename with {rand-guid}
so it’s quite flexible.
Output bindings can be set up in the Integrate tab of the portal. Here I’ve set up four bindings:
I can use these bindings to choose where I want to write the output blob to. Here I’m using the {name}
of the input blob and writing it to a different folder (taking care that this doesn’t cause a recursive triggering of my function!)
My four output blobs are going to each use a different binding technique. Output 1 uses a string
, which we need to declare as byref<string>
. Output 2 uses a TextWriter
. Output 3 uses a Stream
. And Output 4 uses the return value of the function (set up by naming the parameter in the binding $return
), which will be a string
:
let Run(inputBlob: Stream,
name: string,
log: TraceWriter,
output1: byref<string>,
output2: TextWriter,
output3: Stream) =
let message = sprintf "Got blob \n FileName: %s \n Length: %d " name inputBlob.Length
log.Verbose(message)
// output1: string
output1 <- message
// output 2: TextWriter
output2.WriteLine("Hello")
output2.WriteLine("World")
// output 3: Stream
let bytes = Encoding.UTF8.GetBytes("Using CloudBlockBlob directly")
output3.Write(bytes, 0, bytes.Length)
// output 4 is $return, so let's return a string
"Returning via $return"
Finally, you can also use ICloudBlob
and CloudBlockBlob
with blob output bindings, but it’s a little tricky as you have to go into function.json
and set the direction of the binding to “inout
” as explained in this issue on GitHub. These bindings are great for if you want to set metadata on your blob.
Finally, it’s worth remembering that you don’t have to use output bindings if you don’t want to. If the supported bindings are too limited for you (for example you want to have complete control over the output filename), there’s nothing stopping you writing your own code to directly talk to blob storage.
So say we want to use CloudBlockBlob
because we want to set metadata on our blob as well as writing to it. We could make a helper method to get hold of a CloudBlockBlob
from a given storage account setting name, container and blob name:
let GetBlob (storageAcc:string) containerName blobName =
let connString = ConfigurationManager.AppSettings.[storageAcc]
let storageAccount = CloudStorageAccount.Parse(connString)
let blobClient = storageAccount.CreateCloudBlobClient()
let container = blobClient.GetContainerReference(containerName)
container.GetBlockBlobReference(blobName)
Then in our function we could use it to upload to the blob as well as set metadata:
let cloudBlockBlob = GetBlob "AzureWebJobsDashboard" "samples-workitems" "outputdirect/test.txt"
let bytes2 = Encoding.UTF8.GetBytes("Using CloudBlockBlob directly")
log.Verbose("uploading text...")
cloudBlockBlob.UploadFromByteArrayAsync(bytes2, 0, bytes2.Length).Wait()
log.Verbose("text uploaded")
cloudBlockBlob.Metadata.["From"] <- "Mark Heath"
cloudBlockBlob.SetMetadata();
log.Verbose("metadata set")
All the code from this post is available in this GitHub gist and I’ll update it as fixes become available for the poco output bindings become available.