What's the problem

Some time ago, F# became my prefered backend language, taking the place of C#. I am a C# developer since .NET 1.1 and was using it as the primary language for the vast majority of my professional career. Why the change? It's not about language per se but understanding differences between Object-Oriented Programming (OOP) and Functional Programming (FP) paradigms.

I started to see the benefits of Functional Programming, especially around the resiliency of produced code. Sticking to FP principles allowed me to better isolate domain concerns from implementation, and design solutions that would focus on problems I'm solving, rather than implementation and integration aspects of software development.

As much as I love writing F#, I face a challenge of hosting FP code using frameworks that were developed with OOP in mind. They quite often introduce problems that plain FP wouldn't allow to happen.

Dependency Injection is one of the patterns that is commonly used because it solves common OOP problems. It's a fantastic solution for issues that OOP introduces, that would otherwise not happen in the FP approach.

Azure Functions is a great hosting environment for F# and FP code. However, the .NET runtime of Azure Functions is still more friendly to the OOP approach, rather than FP. That's where the need for understanding how to do Dependency Injection in F# might be useful.

This post explains the fundamentals of enabling Dependency Injection in F# Azure Functions.

Before we start

There are a few things you need to follow this post:

To create your own Azure Function, you can follow these steps
dotnet new func -lang F# -n my-function -o .
dotnet add package Microsoft.Azure.Functions.Extensions
dotnet add package Microsoft.Extensions.Http

Now, you can add your first function to the project. Alternatively, you are welcome to use the boilerplate code I've created for you: https://github.com/random82/azure-functions-fsharp-samples/tree/main/function-boilerplate

Enabling dependency injection in Azure Function

Dependency Injection is possible through the Microsoft.Azure.Functions.Extensions package.

We can inject dependencies using IFunctionsHostBuilder object by registering our custom Startup type and overriding the Configure method. Azure Functions allow registration of a custom Startup type through an assembly attribute.

FSharp allows injecting plain functions instead of the whole classes or types. This way, we can create a Dependency Injection contract to a specific function that has a signature required by a consuming function. That's a great way to control the scope of responsibility for objects and functions.

Example of the custom Startup type that exposes IFunctionsHostBuilder which allows injecting dependencies:

namespace Company.Function

open Microsoft.Azure.Functions.Extensions.DependencyInjection
open Microsoft.Extensions.DependencyInjection

type MyStartup() =
    inherit FunctionsStartup()

    override u.Configure(builder: IFunctionsHostBuilder) =
        builder.Services.AddHttpClient() |> ignore

[<assembly: FunctionsStartup(typeof<MyStartup>)>]

do()


Creating an injectable dependency

In this guide, we're going to implement an integer multiplication. Just to demonstrate how to inject one function into other using IoC container available in Azure Functions runtime.

In our Startup.fs file we're going to create a function signature that will act as a contract (same way as interfaces act for classes)

type Multiplier = int -> int -> int

Our F# implementation looks like this:

let multiply x y = x * y

Note: That's the beauty of F# and Functional Programming. Compare above to what you would need to do to create a contract and implementation using C#.

namespace Company.Function

open Microsoft.Azure.Functions.Extensions.DependencyInjection
open Microsoft.Extensions.DependencyInjection

type Multiplier = int -> int -> int

type MyStartup() =
    inherit FunctionsStartup()

    // The function we're going to inject
    let multiply x y = x * y
    override u.Configure(builder: IFunctionsHostBuilder) =
        builder.Services.AddHttpClient() |> ignore
        // We can use plain functions as injected dependencies
        builder.Services.AddSingleton<Multiplier>(multiply) |> ignore

[<assembly: FunctionsStartup(typeof<MyStartup>)>]
do()

Consuming the dependency

We're going to use a type notation for our Azure Function to consume the injected function.


type HttpTrigger(injectedMultiplier: Multiplier) =
    [<FunctionName("HttpTrigger")>]
    member x.Run ([<HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = null)>]req: HttpRequest) (log: ILogger) =

        async {
            log.LogInformation("F# HTTP trigger function processed a request.")
            let x = 2
            let y = 5
            let result = injectedMultiplier x y
            return OkObjectResult(result) :> IActionResult

        } |> Async.StartAsTask

Complete function - single dependency 

In the complete example, we're going to look for arguments x and y passed in GET query string and return a result calculated by injected function.

namespace Company.Function
open System
open Microsoft.AspNetCore.Mvc
open Microsoft.Azure.WebJobs
open Microsoft.Azure.WebJobs.Extensions.Http
open Microsoft.AspNetCore.Http
open Microsoft.Extensions.Logging

// Using type notation for functions allows injections
type HttpTrigger(injectedMultiplier: FSharpFunction.Multiplier) =
    [<Literal>]
    let XParam = "x"
    [<Literal>]
    let YParam = "y"

    let getParamFromQueryString (req:HttpRequest) name =
        if req.Query.ContainsKey(name) then
            let param = req.Query.[name].[0]
            match Int32.TryParse param with
            | true, i -> Some(i)
            | _ -> None
        else
            None

    [<FunctionName("HttpTrigger")>]
    member x.Run ([<HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = null)>]req: HttpRequest) (log: ILogger) =
        async {
            log.LogInformation("F# HTTP trigger function processed a request.")
            let x = getParamFromQueryString req XParam
            let y = getParamFromQueryString req YParam

            match x, y with
            | Some x1, Some y1 ->
                let result = injectedMultiplier x1 y1
                return OkObjectResult(result) :> IActionResult
            | _, _ -> return BadRequestResult() :> IActionResult
        } |> Async.StartAsTask

Adding more dependencies

One of the limitations of injecting plain function is the fact the method signature will work as a dependency identifier in the IoC container, which will result in only one method being called.

If we wanted to expand current implementation with another function, it wouldn't work.


type Multiplier = int -> int -> int
type Adder = int -> int -> int

type MyStartup() =
    inherit FunctionsStartup()

    let multiply x y = x * y
    let add x y = x + y

    override u.Configure(builder: IFunctionsHostBuilder) =
        builder.Services.AddHttpClient() |> ignore
        builder.Services.AddSingleton<Multiplier>(multiply) |> ignore
        builder.Services.AddSingleton<Adder>(add) |> ignore

[<assembly: FunctionsStartup(typeof<MyStartup>)>]
do()



IMPORTANT: Above example won't work because of the limitation. Only one of the registered functions will be called by consuming types. IoC container has no way to distinguish between the implementations.

Both implementations will have the same signature:

FSharpFunc<int, FSharpFunc<int, int>> injectedFunction

Making function types representable with single case unions.

One of the solutions for the shared signatures problem is to use single case unions for the function types.

type Multiplier = MulDef of (int -> int -> int)
type Adder = AddDef of (int -> int -> int)

This way we change dependency signature from

int -> int -> int

to

Company.Function.Adder

and

Company.Function.Multiplier

Which will result in IL generated code that will wrap the signature and expose a unique type that will be properly identified by IoC container. The reverse engineered version of the IL in C# looks like this:

namespace Company.Function
{

    [StructLayout(LayoutKind.Auto, CharSet = CharSet.Auto)]
    public sealed class Adder
    {
        [CompilationMapping(SourceConstructFlags.UnionCase, 0)]
        public static Adder NewAddDef(FSharpFunc<int, FSharpFunc<int, int>> item)
        {
            return new Adder(item);
        }
    }

    // Implementation removed
}


The complete implementation of multiple dependencies would like like this:

namespace Company.Function

open Microsoft.Azure.Functions.Extensions.DependencyInjection
open Microsoft.Extensions.DependencyInjection;

type Multiplier = MulDef of (int -> int -> int)
type Adder = AddDef of (int -> int -> int)

type MyStartup() =
    inherit FunctionsStartup()

    let multiply = MulDef(fun x y -> x * y)
    let add = AddDef(fun x y -> x + y)

    override u.Configure(builder: IFunctionsHostBuilder) =
        builder.Services.AddHttpClient() |> ignore
        builder.Services.AddSingleton<Multiplier>(multiply) |> ignore
        builder.Services.AddSingleton<Adder>(add) |> ignore

[<assembly: FunctionsStartup(typeof<MyStartup>)>]
do()

Because single case unions will wrap the result, we need to match the injected function with the single case union we created and unwrap the underlying implementation.

type HttpMultiplier(injectedMultiplier: Multiplier) =
    [<FunctionName("Multiply")>]
    member x.Multiply ([<HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = null)>]req: HttpRequest)
                          (log: ILogger) =
        async {
            log.LogInformation("F# Mutliply function processed a request.")
            let x = getParamFromQueryString req XParam
            let y = getParamFromQueryString req YParam

            match x, y with
            | Some x1, Some y1 ->
                let result = match injectedMultiplier with MulDef m -> m x1 y1 // << HERE
                return OkObjectResult(result) :> IActionResult
            | _, _ -> return BadRequestResult() :> IActionResult
} |> Async.StartAsTask

The whole example is available here: https://github.com/random82/azure-functions-fsharp-samples/tree/main/dependency-injection

Happy coding!