1

I have the following (rather simplified) code:

public class Service
{
    private IPage? _page;

    private readonly ScreenshotOptions _options;

    public Service(IOptions<ScreenshotOptions> options) => _options = options.Value;

    public async Task CreateScreenshotAsync()
    {
        if (_page == null)
            _page = await InitializePlaywrightAsync();

        await _page.ScreenshotAsync(new PageScreenshotOptions { Path = _options.Filename });

        // all the other stuff
    }

    private async Task<IPage> InitializePlaywrightAsync()
    {
        var playwright = await Playwright.CreateAsync();
        var browser = await playwright.Chromium.LaunchAsync();
        var page = await browser.NewPageAsync();
        await page.SetViewportSizeAsync(_options.Width, _options.Height);

        return page;
    }
}

// Program.cs
builder.Services.AddSingleton<Service>();

I'd prefer to have InitializePlaywrightAsync() not inside Service but let the ASP.NET Core DI container do the work of instantiating IPage. Unfortunately, the following code does not compile:

public class Service
{
    private IPage _page;

    private readonly ScreenshotOptions _options;

    public Service(IOptions<ScreenshotOptions> options, IPage page)
    {
        _options = options.Value;
        _page = page;
    }

    public async Task CreateScreenshotAsync()
    {
        await _page.ScreenshotAsync(new PageScreenshotOptions { Path = _options.Filename });

        // all the other stuff
    }
}

// Program.cs - compilation error
builder.Services.AddSingleton<IPage>((IOptions<ScreenshotOptions> options) => 
{
    var playwright = await Playwright.CreateAsync();
    var browser = await playwright.Chromium.LaunchAsync();
    var page = await browser.NewPageAsync();
    await page.SetViewportSizeAsync(options.Width, options.Height);

    return page;
});
builder.Services.AddSingleton<Service>();

Using a dedicated factory class also is not super nice as the DI container does not accept an asynchronous delegate:

public class PageFactory
{
    private readonly ScreenshotOptions _options;

    public PageFactory(IOptions<ScreenshotOptions> options) => _options = options.Value;

    public async Task<IPage> InitializePlaywrightAsync()
    {
        var playwright = await Playwright.CreateAsync();
        var browser = await playwright.Chromium.LaunchAsync();
        var page = await browser.NewPageAsync();
        await page.SetViewportSizeAsync(_options.Width, _options.Height);

        return page;
    }
}

// Program.cs - compilation error
builder.Services.AddSingleton<IPage>(async provider => await provider.GetRequiredService<PageFactory>().InitializePlaywrightAsync());

I'm curious whether there is any elegant way of moving the InitializePlaywrightAsync() logic into the DI container.

Steven
  • 166,672
  • 24
  • 332
  • 435
mu88
  • 4,156
  • 1
  • 23
  • 47
  • 2
    It seems that there is no other better way, DI container does not allow asynchronous delegation. If you must register it separately, you can put it in another class, but this is really the same as your original code. – Chen Jun 13 '23 at 03:08
  • Related: https://stackoverflow.com/questions/43240405/async-provider-in-net-core-di – Steven Jun 13 '23 at 07:18
  • Related: https://stackoverflow.com/questions/45924027/avoiding-all-di-antipatterns-for-types-requiring-asynchronous-initialization – Steven Jun 13 '23 at 07:18

0 Answers0