This repo shows a minimal example of how to write an Azure function using F# and run it on .NET 5. It also includes an example of deploying to Azure from your local machine and using GitHub actions.
There were several gotchas that were discovered when trying to get this to work which were often tricky to find in the existing documentation. In fact, all of these gotchas are related to using .NET 5 and apply equally to a C# project.
-
Running the function on .NET 5 requires an isolated .NET host. Specifically, we have to set the environment variable
FUNCTIONS_WORKER_RUNTIME
to the value"dotnet-isolated"
. This is because the default host in the functions runtime is still using .NET Core 3.1. We have to set this in both thelocal.settings.json
file for running locally and the ARM template for running in Azure. -
Although our code is targeting
net5.0
we need both the .NET 5 and .NET Core 3.1 SDK installed in order to build the project. More details can be found in this GitHub issue. -
The templates generated by Visual Studio etc don't seem to include the correct NuGet function extensions packages for the isolated hosting model. See the Function.fsproj file for the correct packages to use with the isolated hosting model. Note, this project is using
Microsoft.Azure.Functions.Worker.Extensions.Timer
because this basic example just uses a simple timer trigger, you might need a different extension package if you're using a different trigger, e.g. Blob triggers requireMicrosoft.Azure.Functions.Worker.Extensions.Storage
. -
Again, the templates don't always require all of the necessary MSBuild settings. The .fsproj should contain
<_FunctionsSkipCleanOutput>True</_FunctionsSkipCleanOutput>
. -
Another issue with the stock templates is that the settings files (
host.json
andlocal.settings.json
) have the wrong compile directives, they should use<None Include=>
. -
When publishing an ASP.NET app it's typical to run
dotnet publish
to generate the necessary runtime artifacts such as aweb.config
. Oddly, with an Azure function we just rundotnet build
instead. See the GitHub workflow for an example. -
This one isn't so much of a gotcha, but it's probably worth pointing out that in order to use the
dotnet-isolated
runtime we have to bootstrap the host ourselves. SeeProgram.fs
for a minimal example that works for this simple function. Other functions that use other azure services will need to add the necessary bootstrapping code to configure those services. See the docs on isolated hosts for more information.
Before getting started you'll need an Azure subscription and a resource group, you'll also need to have the az cli installed.
You can create the resource group with the az cli
like this:
az group create -n <resource-group-name> -l <location>
-
Build the function app.
dotnet build src/Function -c Release -o .publish/func
-
Deploy to Azure
./deploy.sh .publish/func/ -g <name-of-your-resource-group>
This repo also includes a GitHub workflow that will run the above steps on each commit to the master
branch.
You will need the following tools installed on your machine, see this document for full instructions:
-
Ensure the local storage connection string is set. In local.settings.json the following config value should be set under the
Values
section."AzureWebJobsStorage": "UseDevelopmentStorage=true"
This tells the function to look for a storage account on the default development port and with the default development account key.
-
Start Azurite (alter the paths if you want to use different locations to store data and logs)
azurite --location ~/.azurite --debug ~/.azurite/debug.log
-
Start the function
# Must be run from the src/Function directory func start
Open an issue and let me know.