The preceding interface exposes the two clients we had Refit generate for us. Its implementation is fairly straightforward as well:
public class DefaultWebClient : IWebClient
{
public DefaultWebClient(IBasketsClient baskets, IProductsClient catalog)
{
Baskets = baskets ??
throw new ArgumentNullException(nameof(baskets));
Catalog = catalog ??
throw new ArgumentNullException(nameof(catalog));
}
public IBasketsClient Baskets { get; }
public IProductsClient Catalog { get; }
}
The preceding default implementation composes itself through constructor injection, exposing the two typed clients.Of course, dependency injection means we must register services with the container. Let’s start with some configuration. To make the setup code parametrizable and allow the Docker container to override those values, we extract the services base addresses to the settings file like this (appsettings.Development.json):
{
“Downstream”: {
“Baskets”: {
“BaseAddress”: “https://localhost:60280”
},
“Products”: {
“BaseAddress”: “https://localhost:57362”
}
}
}
The preceding code defines two keys, one per service, which we then load individually in the Program.cs file, like this:
using Refit;
using REPR.BFF;
using System.Collections.Concurrent;
using System.Net;
var builder = WebApplication.CreateBuilder(args);
var basketsBaseAddress = builder.Configuration
.GetValue<string>(“Downstream:Baskets:BaseAddress”) ??
throw new NotSupportedException(“Cannot start the program without a Baskets base address.”);
var productsBaseAddress = builder.Configuration
.GetValue<string>(“Downstream:Products:BaseAddress”) ??
throw new NotSupportedException(“Cannot start the program without a Products base address.”);
The preceding code loads the two configurations into variables.
We can leverage all the techniques we learned in Chapter 9, Options, Settings, and Configuration, to create a more elaborate system.
Next, we register our Refit clients like this:
builder.Services
.AddRefitClient<IBasketsClient>()
.ConfigureHttpClient(c => c.BaseAddress = new Uri(basketsBaseAddress))
;
builder.Services
.AddRefitClient<IProductsClient>()
.ConfigureHttpClient(c => c.BaseAddress = new Uri(productsBaseAddress))
;
In the preceding code, calling the AddRefitClient method replaces the .NET AddHttpClient method and registers our auto-generated client with the container. Because Refit registration returns an IHttpClientBuilder interface, we can use the ConfigureHttpClient method to configure the HttpClient as we would any other typed HTTP client. In this case, we set the BaseAddress property to the values of the previously loaded settings.Next, we must also register our aggregate:
builder.Services.AddTransient<IWebClient, DefaultWebClient>();
I picked a transient state because the service only fronts other services, so it will serve the other services as they are registered, regardless of whether it is the same instance every time. Moreover, it needs a transient or scoped lifetime because the BFF must manage who is the current customer, not the client. It would be quite a security vulnerability to allow users to decide who they want to impersonate for every request.
The project does not authenticate the users, but the service we explore next is designed to make this evolve, abstracting and managing this responsibility so we could add authentication without impacting the code we are writing.
Let’s explore how we manage the current user.
Creating a service that serves the current customer
To keep the project simple, we are not using any authentication or authorization middleware, yet we want our BFF to be realistic and to handle who’s querying the downstream APIs. To achieve this, let’s create the ICurrentCustomerService interface that abstracts this away from the consuming code:
public interface ICurrentCustomerService
{
int Id { get; }
}
The only thing that interface does is provide us with the identifier representing the current customer. Since we do not have authentication in the project, let’s implement a development version that always returns the same value:
public class FakeCurrentCustomerService : ICurrentCustomerService
{
public int Id => 1;
}
Finally, we must register it in the Program.cs class like this:
builder.Services.AddScoped<ICurrentCustomerService, FakeCurrentCustomerService>();
With this last piece, we are ready to write some features in our BFF service.
In a project that uses authentication, you can inject the IHttpContextAccessor interface into a class to access the current HttpContext object that contains a User property that enables access to the current user’s ClaimsPrincipal object, which should include the current user’s CustomerId. Of course, you must ensure the authentication server returns such a claim. You must register the accessor using the following method before using it: builder.Services.AddHttpContextAccessor().