Search code examples
f#dotnet-httpclientbolero

Managing HttpClient in a F# WebAssembly app


What is the best practice is to "register" the http client in one place, so it can be reused from this Elmish update function? Instead of having to create it for every request.

let update message model =
    match message with
    | SetMessage s -> { model with x = s }
    | Loading -> { model with x = "Doing something long ..." }

let handleClick model dispatch _ = 
    dispatch Loading
    async {
        let url = Uri "https://api.github.com"
        -- FIXME: too expensive to do this on per-update basis
        use httpClient = new HttpClient(BaseAddress = url)
        let! resp = httpClient.GetAsync "/users/srid" |> Async.AwaitTask
        let! s = resp.Content.ReadAsStringAsync() |> Async.AwaitTask
        dispatch (SetMessage s)
    } |> Async.Start

I feel like this would normally go in Startup.fs. I use a client-only Bolero web app, so this would look like:

 builder.Services.AddSingleton<HttpClient>(new HttpClient (BaseAddress=apiBase))

But then the question becomes ... how do I access it from my program in F#? What is the idiomatic way?


Solution

  • Probably the best way would either be to add HttpClient as another field in your model or as another parameter to your update function.

    let update (client:HttpClient) message model = // Your code
    
    let url = Uri "https://api.github.com"
    let httpClient = new HttpClient(BaseAddress = url)
    

    In general you shouldn't "do work" in your view and, by extension, event handlers. Instead, you should use the Elmish Cmd module something like this:

    let update httpClient message model =
        match message with
        | SetMessage s -> 
            { model with x = s }, Cmd.none
        | GetMessageAsync -> 
            let cmd = 
                let getHttp () = 
                    async {
                        let! resp = httpClient.GetAsync "/users/srid" |> Async.AwaitTask
                        return! resp.Content.ReadAsStringAsync() |> Async.AwaitTask
                    }
                Cmd.OfAsync.perform getHttp () (fun s -> SetMessage s)
    
            { model with x = "Doing something long ..." }, cmd
    
    let handleClick model dispatch _ = 
        dispatch GetMessageAsync