United Kingdom: +44 (0)208 088 8978

Dependency Injection with ASP.NET and F#

Ryan gives a brief overview of dependency injection in .NET and how to use it with F# and the SAFE Stack.

We're hiring Software Developers

Click here to find out more

Whilst F# has complete support for classes and object-oriented programming, it is not the idiomatic way of writing software using the language.

We tend to favour separating state and behaviour, and usually default to immutable data structures unless performance dictates otherwise.

Because of this, conversations regarding dependency resolution usually focus on function composition and abstraction.

When interacting with libraries in the wider .NET ecosystem, particularly ASP.NET, you will often encounter instructions which ask you to register services with the framework at startup, advising you to resolve them later through your class constructors.

The docs go some way to describing the system but focus on C#.

In this blog I will show you how this translates to F#, particularly the SAFE Stack.

Registering Services

ASP.NET provides the IServiceCollection interface which allows you to register services.

The place this is done is usually at the point you build up and run your web application.

In a C# app this would be the Startup.cs class. In a SAFE stack project the equivalent is the Server.fs module.

Here you will find an app builder provided by Saturn. This gives you various hooks into the application configuration.

This includes service_config, to which you can pass an IServiceCollection -> IServiceCollection function.

That function is the place at which you can register services.

let configureServices (services : IServiceCollection) =
    services.AddHttpClient()

let app =
    application {
        // ...
        service_config configureServices
    }

If you aren't using Saturn, you can achieve the same thing with Giraffe alone or even just plain ASP.NET.

Many services, such as HttpClient above, come with extension methods on the IServiceCollection which just allow you to call .AddXXX().

You can also create and register any type or instance you like as a service.

To do this you use IServiceCollection methods such as 'AddScoped', 'AddSession', 'AddSingleton' and 'AddTransient' which allow you to register services with different lifetimes.

Resolving services for HTTP requests

Once registered, you can resolve services using the ServiceProvider class.

Every HTTP request has an accompanying HttpContext which provides access to the ServiceProvider.

The SAFE stack comes with Giraffe which provides a handy extension to HttpContext that allows you to use context.GetService<'T>() in order to resolve a dependency easily.

If you aren't using Giraffe, you just need to call context.RequestServices.GetService<'T>() instead.

If using the SAFE stack with Fable.Remoting, you may do something like this:

let doSomething (myService : IMyService) =
    ()

let myApi (ctx: HttpContext) : IMyApi =
    let myService = ctx.RequestServices.GetService<IMyService>()
    { myEndpoint = doSomething myService }

let myApp =
    Remoting.createApi ()
    |> Remoting.fromContext myApi
    |> Remoting.withErrorHandler errorHandler
    |> Remoting.buildHttpHandler

If you are just using Giraffe or Saturn without Fable Remoting, for instance to create a headless api, you have access to the HTTP context in all of your HTTP handler functions.

Resolving through constructor injection

There are times when you will not have a HTTP context available from which to resolve services.

One such example is when using WebJobs, as these can be triggered by things such as timers or queue messages.

As I demonstrate in my previous blog on the topic, you can just place any services you require in the constructor of your WebJobs class and they will be automatically injected by the framework.

This also works for any services you have registered yourself. When you resolve them, the framework will provide all of their dependencies.

type IMyService (someOtherService : ISomeOtherService) =
    abstract member DoStuff : unit -> unit

type WebJobs (myService : IMyService) =
    member this.TimerFired ([<TimerTrigger "0 0/1 * * * *">] timer:TimerInfo) = task {
        myService.DoStuff()
    }

Best practices

So, now we know how to register and resolve services, the question is when and where should we do it?

This is really down to personal preference, but I can explain our general practice.

  1. If working with our own code only, we don't tend to use this approach at all, preferring to just do 'normal' functional composition. The exception to this would be if we needed the lifecycle management of stateful services that the service container can provide.

  2. If working with third party services which recommend you register their services, perhaps providing extension methods, we will do so. We will then usually resolve the service as early as possible in the HTTP handler, so that we don't have to pass the HTTP context into our application code.

  3. If working with frameworks such as WebJobs which need to resolve services without a HTTP context available, we will use constructor injection.