FlUrl 4.0: how to work with WithOAuthBearerToken and dependency container

130 views Asked by At

In FlUrl version 3.X, I have been working with my REST Api in this way:

  • I have the singleton instance _noAuthClient. With this client, I make a Login call to Rest Api, which returns JWT token.
  • Then I am creating another singleton instance _authClient with the call below and all subsequent calls are done through this _authClient. This ensures compatibility with AspNet.Core authorization.
    private IFlurlClient CreateAuthClient()
    {
        lock (_authLock)
        {
            if (AuthToken == null)
            {
                throw new InvalidOperationException("AuthenticateAsync was not called. Client can be accessed after AuthenticateAsync() call.");
            }

            return _noAuthClient.WithOAuthBearerToken(AuthToken.AuthToken);
        }
    }

I think that in 4.0 this approach with singleton clients is already deprecated and I would like to use a more current approach. Could you give me some direction? I am implementing 4.0 Flurl with Dependency container. Registering IFlurlClientCache as a singleton, configuring by .WithDefaults(builder => ...).

I have found this interesting solution, but this is also not aligned with 4.0.

1

There are 1 answers

1
Todd Menier On

For using DI and needing precise control of your client instances, I recommend using named clients, an idea that was...uh...borrowed from .NET's IHttpClientFactory. Although you can pre-configure your clients during DI registration, I think you'll find that GetOrAdd is the way to go here, since you won't have that auth token at startup (I'm assuming).

One thing to look out for is that in your code sample, _noAuthClient.WithOAuthBearerToken doesn't return a new client - it modifies the original. To have 2 instances, they each need to be created/configured from scratch.

But that doesn't mean you can't use a shared method for both. I'd suggest something like this in your service class:

private IFlurlClient GetOrCreateClient(bool auth)
{
    if (auth && AuthToken == null)
        throw ...

    var name = "some-service-" + (auth ? "auth" : "anon");

    return _clientCache.GetOrAdd(name, "https://some-service.com", builder => {
        // configure builder for either client
        
        if (auth)
            builder.WithOAuthBearerToken(AuthToken.AuthToken);
    });
}

Here _clientCache is your injected IFlurlClientCache singleton. GetOrAdd is thread-safe and ensures that the builder action happens lazily and at most once per client name; no need for locks here. All other methods in your class can simply call GetOrCreateClient(...).Request(...) as needed to invoke Flurl calls, and no matter how often that happens, the GetOrAdd behavior ensures that no more than 2 clients are created/configured for the life of your application.