Azure Functions F# Developer Reference

F# for Azure Functions is a solution for easily running small pieces of code, or "functions," in the cloud. Data flows into your F# function via function arguments. Argument names are specified in function.json, and there are predefined names for accessing things like the function logger and cancellation tokens.

This article assumes that you've already read the Azure Functions developer reference.

How .fsx works

An .fsx file is an F# script. It can be thought of as an F# project that's contained in a single file. The file contains both the code for your program (in this case, your Azure Function) and directives for managing dependencies.

When you use an .fsx for an Azure Function, commonly required assemblies are automatically included for you, allowing you to focus on the function rather than "boilerplate" code.

Binding to arguments

Each binding supports some set of arguments, as detailed in the Azure Functions triggers and bindings developer reference. For example, one of the argument bindings a blob trigger supports is a POCO, which can be expressed using an F# record. For example:

type Item = { Id: string }

let Run(blob: string, output: byref<Item>) =
    let item = { Id = "Some ID" }
    output <- item

Your F# Azure Function will take one or more arguments. When we talk about Azure Functions arguments, we refer to input arguments and output arguments. An input argument is exactly what it sounds like: input to your F# Azure Function. An output argument is mutable data or a byref<> argument that serves as a way to pass data back out of your function.

In the example above, blob is an input argument, and output is an output argument. Notice that we used byref<> for output (there's no need to add the [<Out>] annotation). Using a byref<> type allows your function to change which record or object the argument refers to.

When an F# record is used as an input type, the record definition must be marked with [<CLIMutable>] in order to allow the Azure Functions framework to set the fields appropriately before passing the record to your function. Under the hood, [<CLIMutable>] generates setters for the record properties. For example:

[<CLIMutable>]
type TestObject =
    { SenderName : string
      Greeting : string }

let Run(req: TestObject, log: TraceWriter) =
    { req with Greeting = sprintf "Hello, %s" req.SenderName }

An F# class can also be used for both in and out arguments. For a class, properties will usually need getters and setters. For example:

type Item() =
    member val Id = "" with get,set
    member val Text = "" with get,set

let Run(input: string, item: byref<Item>) =
    let result = Item(Id = input, Text = "Hello from F#!")
    item <- result

Logging

To log output to your streaming logs in F#, your function should take an argument of type TraceWriter. For consistency, we recommend this argument is named log. For example:

let Run(blob: string, output: byref<string>, log: TraceWriter) =
    log.Verbose(sprintf "F# Azure Function processed a blob: %s" blob)
    output <- input

Async

The async workflow can be used, but the result needs to return a Task. This can be done with Async.StartAsTask, for example:

let Run(req: HttpRequestMessage) =
    async {
        return new HttpResponseMessage(HttpStatusCode.OK)
    } |> Async.StartAsTask

Cancellation Token

If your function needs to handle shutdown gracefully, you can give it a CancellationToken argument. This can be combined with async, for example:

let Run(req: HttpRequestMessage, token: CancellationToken)
    let f = async {
        do! Async.Sleep(10)
        return new HttpResponseMessage(HttpStatusCode.OK)
    }
    Async.StartAsTask(f, token)

Importing namespaces

Namespaces can be opened in the usual way:

open System.Net
open System.Threading.Tasks

let Run(req: HttpRequestMessage, log: TraceWriter) =
    ...

The following namespaces are automatically opened:

  • System
  • System.Collections.Generic
  • System.IO
  • System.Linq
  • System.Net.Http
  • System.Threading.Tasks
  • Microsoft.Azure.WebJobs
  • Microsoft.Azure.WebJobs.Host.

Referencing External Assemblies

Similarly, framework assembly references can be added with the #r "AssemblyName" directive.

#r "System.Web.Http"

open System.Net
open System.Net.Http
open System.Threading.Tasks

let Run(req: HttpRequestMessage, log: TraceWriter) =
    ...

The following assemblies are automatically added by the Azure Functions hosting environment:

  • mscorlib,
  • System
  • System.Core
  • System.Xml
  • System.Net.Http
  • Microsoft.Azure.WebJobs
  • Microsoft.Azure.WebJobs.Host
  • Microsoft.Azure.WebJobs.Extensions
  • System.Web.Http
  • System.Net.Http.Formatting.

In addition, the following assemblies are special cased and may be referenced by simplename (e.g. #r "AssemblyName"):

  • Newtonsoft.Json
  • Microsoft.WindowsAzure.Storage
  • Microsoft.ServiceBus
  • Microsoft.AspNet.WebHooks.Receivers
  • Microsoft.AspNEt.WebHooks.Common.

If you need to reference a private assembly, you can upload the assembly file into a bin folder relative to your function and reference it by using the file name (e.g. #r "MyAssembly.dll"). For information on how to upload files to your function folder, see the following section on package management.

Editor Prelude

An editor that supports F# Compiler Services will not be aware of the namespaces and assemblies that Azure Functions automatically includes. As such, it can be useful to include a prelude that helps the editor find the assemblies you are using, and to explicitly open namespaces. For example:

#if !COMPILED
#I "../../bin/Binaries/WebJobs.Script.Host"
#r "Microsoft.Azure.WebJobs.Host.dll"
#endif

open Sytem
open Microsoft.Azure.WebJobs.Host

let Run(blob: string, output: byref<string>, log: TraceWriter) =
    ...

When Azure Functions executes your code, it processes the source with COMPILED defined, so the editor prelude will be ignored.

Package management

To use NuGet packages in an F# function, add a project.json file to the the function's folder in the function app's file system. Here is an example project.json file that adds a NuGet package reference to Microsoft.ProjectOxford.Face version 1.1.0:

{
  "frameworks": {
    "net46":{
      "dependencies": {
        "Microsoft.ProjectOxford.Face": "1.1.0"
      }
    }
   }
}

Only the .NET Framework 4.6 is supported, so make sure that your project.json file specifies net46 as shown here.

When you upload a project.json file, the runtime gets the packages and automatically adds references to the package assemblies. You don't need to add #r "AssemblyName" directives. Just add the required open statements to your .fsx file.

You may wish to put automatically references assemblies in your editor prelude, to improve your editor's interaction with F# Compile Services.

How to add a project.json file to your Azure Function

  1. Begin by making sure your function app is running, which you can do by opening your function in the Azure portal. This also gives access to the streaming logs where package installation output will be displayed.
  2. To upload a project.json file, use one of the methods described in how to update function app files. If you are using Continuous Deployment for Azure Functions, you can add a project.json file to your staging branch in order to experiment with it before adding it to your deployment branch.
  3. After the project.json file is added, you will see output similar to the following example in your function's streaming log:
2016-04-04T19:02:48.745 Restoring packages.
2016-04-04T19:02:48.745 Starting NuGet restore
2016-04-04T19:02:50.183 MSBuild auto-detection: using msbuild version '14.0' from 'D:\Program Files (x86)\MSBuild\14.0\bin'.
2016-04-04T19:02:50.261 Feeds used:
2016-04-04T19:02:50.261 C:\DWASFiles\Sites\facavalfunctest\LocalAppData\NuGet\Cache
2016-04-04T19:02:50.261 https://api.nuget.org/v3/index.json
2016-04-04T19:02:50.261
2016-04-04T19:02:50.511 Restoring packages for D:\home\site\wwwroot\HttpTriggerCSharp1\Project.json...
2016-04-04T19:02:52.800 Installing Newtonsoft.Json 6.0.8.
2016-04-04T19:02:52.800 Installing Microsoft.ProjectOxford.Face 1.1.0.
2016-04-04T19:02:57.095 All packages are compatible with .NETFramework,Version=v4.6.
2016-04-04T19:02:57.189
2016-04-04T19:02:57.189
2016-04-04T19:02:57.455 Packages restored.

Environment variables

To get an environment variable or an app setting value, use System.Environment.GetEnvironmentVariable, for example:

open System.Environment

let Run(timer: TimerInfo, log: TraceWriter) =
    log.Info("Storage = " + GetEnvironmentVariable("AzureWebJobsStorage"))
    log.Info("Site = " + GetEnvironmentVariable("WEBSITE_SITE_NAME"))

Reusing .fsx code

You can use code from other .fsx files by using a #load directive. For example:

run.fsx

#load "logger.fsx"

let Run(timer: TimerInfo, log: TraceWriter) =
    mylog log (sprintf "Timer: %s" DateTime.Now.ToString())

logger.fsx

let mylog(log: TraceWriter, text: string) =
    log.Verbose(text);

Paths provides to the #load directive are relative to the location of your .fsx file.

  • #load "logger.fsx" loads a file located in the function folder.
  • #load "package\logger.fsx" loads a file located in the package folder in the function folder.
  • #load "..\shared\mylogger.fsx" loads a file located in the shared folder at the same level as the function folder, that is, directly under wwwroot.

The #load directive only works with .fsx (F# script) files, and not with .fs files.

Next steps

For more information, see the following resources: