C
C#2y ago
revolt

❔ Properly calling "await login()" in web service constructor

Hello. Kinda noob, but I am looking for the best way to call a async login function for an external library in a web service constructor. I posted my code below. I want to use this service in DI singleton and make sure the _client.ConnectAsync() is called once correctly. AFAIK this approach would require me to call EnsureInitializedAsync(); everytime I use this service in my api controller. I just want to know if this is the best approach, or if there is something easier to use. Thank you!
namespace web.Services
{
public class SSService
{
private readonly Lazy<Task> _initialization;
private Client _client;

public SSService()
{
_initialization = new Lazy<Task>(InitializeAsync);
}

private async Task InitializeAsync()
{
_client = new Client();
await _client.ConnectAsync("test", "test");
}

public Task EnsureInitializedAsync() => _initialization.Value;
}
}
namespace web.Services
{
public class SSService
{
private readonly Lazy<Task> _initialization;
private Client _client;

public SSService()
{
_initialization = new Lazy<Task>(InitializeAsync);
}

private async Task InitializeAsync()
{
_client = new Client();
await _client.ConnectAsync("test", "test");
}

public Task EnsureInitializedAsync() => _initialization.Value;
}
}
6 Replies
lycian
lycian2y ago
are you guaranteed that InitializeAsync has already completed? If so then you don't need to call EnsureInitialized
revolt
revoltOP2y ago
Thanks for the reply. not sure exactly what you mean. If the Connect method wasnt asynchronous I would just throw it in the constructor directly, and then as a singleton Connect would just be called once on program start automatically, correct? I'm pretty much just asking how to replicate that behaviour. Since it is asynchronous and contructor cant call asynchronous methods directly, this is the workaround to do it. The only problem now is that I have to call InitializeAsync somewhere. So if im in the web controller, instead of just accessing SSService with DI and _client would be connected already, I have to make sure that it is connected by calling EnsureInitializedAsync. - If connected, do nothing, if not InitializeAsync will be called automatically. If there is another place where I can just call InitializeAsync once on program start, that would be ideal instead of calling EnsureInitializedAsync everytime SSService is used. Hopefully I am clear enough, because again i'm kind of a beginner so just trying to wrap my head around this. Thanks!
Becquerel
Becquerel2y ago
the standard approach to this is to hide the normal ctor for this class and only have a static async factory function e.g.
namespace web.Services
{
public class SSService
{
private Client _client;

// Nobody else can directly make the class
private SSService()
{
}

// Use in place of the normal constructor
public static async Task<SSService> Create()
{
_client = new Client();
await _client.ConnectAsync("test", "test");
}
}
}
namespace web.Services
{
public class SSService
{
private Client _client;

// Nobody else can directly make the class
private SSService()
{
}

// Use in place of the normal constructor
public static async Task<SSService> Create()
{
_client = new Client();
await _client.ConnectAsync("test", "test");
}
}
}
re: DI, i don't think microsoft's framework supports async initialization. so you have to do something like this
// Construct manually outside of DI
var ssservice = await SSService.Create();

var services = new ServiceCollection();

// Add it as an instance, not as a generic type.
services.AddSingleton(ssservice);

// Build your service provider as normal.
// Construct manually outside of DI
var ssservice = await SSService.Create();

var services = new ServiceCollection();

// Add it as an instance, not as a generic type.
services.AddSingleton(ssservice);

// Build your service provider as normal.
lycian
lycian2y ago
^ that, or use the factory pattern. I wasn't sure if you were just injecting the service with DI or using some other method. I've been digging into razor recently and likely what I would do is
@inject MyServiceFactory ServiceFactory

@code
{
MyService? Service = null;

protected override async Task OnInitializedAsync()
{
Service = await ServiceFactory.CreateOrGetAsync();
}
}
@inject MyServiceFactory ServiceFactory

@code
{
MyService? Service = null;

protected override async Task OnInitializedAsync()
{
Service = await ServiceFactory.CreateOrGetAsync();
}
}
for just asp.net web server, you can achieve this without a factory since you can separate initialize and run
// build services etc before here
await InitializeAsyncServices(app);

app.Run();

static async Task InitializeAsyncServices(WebApplication app)
{
var service = app.Services.GetService<MyService>();
await service.InitializeAsync();
}
// build services etc before here
await InitializeAsyncServices(app);

app.Run();

static async Task InitializeAsyncServices(WebApplication app)
{
var service = app.Services.GetService<MyService>();
await service.InitializeAsync();
}
Unknown User
Unknown User2y ago
Message Not Public
Sign In & Join Server To View
Accord
Accord2y ago
Was this issue resolved? If so, run /close - otherwise I will mark this as stale and this post will be archived until there is new activity.
Want results from more Discord servers?
Add your server