C
C#7mo ago
oe

Refactoring Injected Singleton for Authentication and HttpClient Usage

Everytime a TestPaymentsService is created, it should be authenticated. Instead of constantly calling .Authenticate, I want to be able to do it once at the start of my app's lifetime. Instead of firing off a web request in the constructor (since it cannot be async), I decided to create a static create method that creates the instance to be able to authenticate. However, I now realised its a bad practice to recreate a HttpClient in every method inside TestPaymentsService. Although I registered a IHttpClientFactory using builder.Services.AddHttpClient(); in Program.cs, I cannot inject it since I dont have a constructor. I want to be able to create a TestPaymentsService, inject it into DI and use it wherever I want without having to call Authenticate method each time, since it was called at the very beginning. How should I approach this? Current implementation: Program.cs:
var builder = WebApplication.CreateBuilder(args);
...
builder.Services.AddSingleton(await TestPaymentsService.CreateAsync());
...
builder.Services.AddHttpClient();
var builder = WebApplication.CreateBuilder(args);
...
builder.Services.AddSingleton(await TestPaymentsService.CreateAsync());
...
builder.Services.AddHttpClient();
TestPaymentsService.cs:
public class TestPaymentsService
{
private string? jwtToken { get; set; }

public static async Task<TestPaymentsService> CreateAsync()
{
var client = new TestPaymentsService();
await client.Authenticate();
return client;
}
...
public async Task Authenticate()
{
using var client = new HttpClient();
...
public class TestPaymentsService
{
private string? jwtToken { get; set; }

public static async Task<TestPaymentsService> CreateAsync()
{
var client = new TestPaymentsService();
await client.Authenticate();
return client;
}
...
public async Task Authenticate()
{
using var client = new HttpClient();
...
2 Replies
oe
oe7mo ago
Fixed like so... any objections?
public class TestPaymentsService
{
private HttpClient client;

public TestPaymentsService(IHttpClientFactory factory)
{
client = factory.CreateClient();
}

(removed the static builder method)

Program.cs:

...
builder.Services.AddSingleton<TestPaymentsService>();
...
WebApplication app = builder.Build();
...
await app.Services.GetRequiredService<TestPaymentsService>().Authenticate();
public class TestPaymentsService
{
private HttpClient client;

public TestPaymentsService(IHttpClientFactory factory)
{
client = factory.CreateClient();
}

(removed the static builder method)

Program.cs:

...
builder.Services.AddSingleton<TestPaymentsService>();
...
WebApplication app = builder.Build();
...
await app.Services.GetRequiredService<TestPaymentsService>().Authenticate();
PixxelKick
PixxelKick7mo ago
Youll need to open up a scope, but, instead I would put a private method on your service called something like EnsureAuthenticatedAsync() and just call it at the start of your methods. If it is already authenticated it just returns and short circuits out. This is good practice because, chances are, later on you'll be able to easily adapt to things like "what if your authentication method can expire?" Its common for bearer tokens or whatever you use to authenticate to have an expiry attached, you'd then be able to track that as a private variable and your Ensure method can check that it's old token/cookie/etc isn't expired as well as part of its logic, and if it is expired, renew it