Azure Functions in F# (For Real)

If you’ve ever glanced at Azure Functions and F#, you might think they were made for each other. And yet if you want to create a new Azure Function project in Visual Studio, C# is apparently your only option.

Maybe someday, Visual Studio will include support for Azure Functions in F#, but for now it’s possible to get there by adapting the C# Azure Function template. After all, F# is a first-class language on the .NET CLR, and it’s all the same once it’s compiled anyway.

It’s important to note that I’m talking about compiled F# functions—not the fsx script that you get when creating a function through the Azure web portal.

Project Files

Starting a new Azure Function project in Visual Studio 2017 generates these five files:

  • FunctionApp1.csproj
  • Function1.cs
  • host.json
  • local.settings.json
  • .gitignore

The C# project file:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net461</TargetFramework>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Microsoft.NET.Sdk.Functions" Version="1.0.13" />
  </ItemGroup>
  <ItemGroup>
    <Reference Include="Microsoft.CSharp" />
  </ItemGroup>
  <ItemGroup>
    <None Update="host.json">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="local.settings.json">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
      <CopyToPublishDirectory>Never</CopyToPublishDirectory>
    </None>
  </ItemGroup>
</Project>

We can convert the C# project file into an F# project file just by changing a few lines:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net461</TargetFramework>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Microsoft.NET.Sdk.Functions" Version="1.0.13" />
  </ItemGroup>
  <ItemGroup>
    <Compile Include="Function1.fs" />
  </ItemGroup>
  <ItemGroup>
    <Content Include="host.json">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </Content>
    <Content Include="local.settings.json">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
      <CopyToPublishDirectory>Never</CopyToPublishDirectory>
    </Content>
  </ItemGroup>
</Project>

I’ve removed the reference to the Microsoft.CSharp assembly and added a section that lists F# source files (because in F# order matters), including a new Function1.fs.

For some reason, the lines in the C# project file that copy host.json and local.settings.json don’t work in the F# project (those files are simply missing from the output directory). In order to include them, I had to change the item type from None to Content.

Unfortunately, this causes these files to be required, and the build will fail if they’re gone. That’s not a problem for host.json, but the .gitignore from the C# template includes local.settings.json!

The easiest thing to do is just remove that rule from .gitignore and make sure local.settings.json doesn’t contain any secrets.

Source Files

Now onto the source files. For reference, here’s what a C# timer-triggered Azure function template looks like:

using System;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Host;

namespace FunctionApp1
{
    public static class Function1
    {
        [FunctionName("Function1")]
        public static void Run([TimerTrigger("0 */5 * * * *")] TimerInfo myTimer, TraceWriter log)
        {
            log.Info($"C# Timer trigger function executed at: {DateTime.Now}");
        }
    }
}

Porting this to F# is pretty straightforward:

namespace FunctionApp1

open System
open Microsoft.Azure.WebJobs
open Microsoft.Azure.WebJobs.Host

module Function1 =

    [<FunctionName("Function1")>]
    let Run ([<TimerTrigger("0 */5 * * * *")>] myTimer: TimerInfo) (log: TraceWriter) =
        log.Info(sprintf "F# Timer trigger function executed at: %s" (DateTime.Now.ToString()))

The hard part is done! Those other files (host.json and local.settings.json) are used by the Azure Function host, so no changes are necessary there.

All that’s left is to build the project and then either run it locally or publish it to Azure.

Publishing

In Visual Studio, a C# Azure Function is endowed with a “Publish…” context menu item. Since our F# project amounts to a plain F# library, we don’t get the wizard. But this is hardly a problem since the command-line tools can handle publishing just fine.

First, install the Azure Functions Core Tools. Then you can either run your function locally, or publish it to Azure and run it there.

To run your function locally:

  1. Build the project (using Visual Studio, or dotnet build from your project directory).
  2. Switch to the build artifacts directory (e.g. bin\Debug\net461).
  3. Run func host start.

To publish to Azure:

  1. Create an Azure Function resource using the Azure web portal or az command.
  2. From your project directory, run dotnet publish -c Release.
  3. Switch to the publish output directory (e.g. bin\Release\net461\publish).
  4. Run func azure functionapp publish <function-app-name>.

F# Azure Function Template

To get started quickly, here is a simple template for an Azure timer function (zip download) using the files I’ve referenced in this post. But since Microsoft is still actively developing Azure’s Function infrastructure, I look forward to better tooling support soon!

Conversation
  • Mikhail says:

    The tooling for F# support in Azure Functions should improve soon, including the templates for Func CLI, Visual Studio and Code.

    I wouldn’t recommend removing local.settings.json from .gitignore. It has to exist for pretty much any Function App except HTTP-only anyway, and it’s very likely that people will start putting secrets inside (storage / service bus / cosmos db connection string etc).

    Another intro to precompiled F# Functions can be found at my blog https://mikhail.io/2017/12/precompiled-azure-functions-in-fsharp/ with some examples in https://github.com/mikhailshilkov/azure-functions-fsharp-examples

  • Comments are closed.