You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在ASP.NET Core DI容器中注册带参数的异步工厂方法

ASP.NET Core DI 中异步注册 Playwright IPage 的优雅方案

问题背景

现有Service类内部通过异步方法初始化Playwright的IPage,想把这块初始化逻辑移到DI容器里管理,但直接用异步委托注册会编译失败——因为ASP.NET Core原生DI不支持异步注册委托。

原实现代码:

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 });
        // 其他业务逻辑
    }

    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>();

不管是直接写异步注册委托,还是用工厂类调用异步方法,都会触发编译错误,因为DI容器的注册入口只接受同步委托。

优雅解决方案

方案1:启动阶段异步初始化(推荐)

在应用启动时提前完成IPage的异步初始化,再把实例注册为DI单例。同时要把Playwright和Browser也注册进去,确保后续能正确释放资源。

var builder = WebApplication.CreateBuilder(args);

// 1. 绑定配置项
builder.Services.Configure<ScreenshotOptions>(builder.Configuration.GetSection("ScreenshotOptions"));

// 2. 直接从配置读取ScreenshotOptions
var screenshotOptions = builder.Configuration
    .GetSection("ScreenshotOptions")
    .Get<ScreenshotOptions>() 
    ?? throw new InvalidOperationException("未配置ScreenshotOptions");

// 3. 异步初始化Playwright、Browser、IPage
var playwright = await Playwright.CreateAsync();
var browser = await playwright.Chromium.LaunchAsync();
var page = await browser.NewPageAsync();
await page.SetViewportSizeAsync(screenshotOptions.Width, screenshotOptions.Height);

// 4. 注册为单例
builder.Services.AddSingleton(playwright);
builder.Services.AddSingleton(browser);
builder.Services.AddSingleton<IPage>(page);

// 5. 注册业务Service
builder.Services.AddSingleton<Service>();

var app = builder.Build();

// 后续中间件配置...
app.Run();

方案2:异步工厂包装类(延迟初始化)

如果希望IPage在第一次被使用时才初始化,可以封装一个异步工厂类,在Service中注入工厂并按需获取实例。

定义工厂类(带资源释放)

public interface IPageFactory
{
    Task<IPage> GetPageAsync();
}

public class PageFactory : IPageFactory, IAsyncDisposable
{
    private readonly ScreenshotOptions _options;
    private IPage? _page;
    private IPlaywright? _playwright;
    private IBrowser? _browser;

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

    public async Task<IPage> GetPageAsync()
    {
        if (_page == null)
        {
            _playwright = await Playwright.CreateAsync();
            _browser = await _playwright.Chromium.LaunchAsync();
            _page = await _browser.NewPageAsync();
            await _page.SetViewportSizeAsync(_options.Width, _options.Height);
        }
        return _page;
    }

    // 实现异步释放,避免资源泄漏
    public async ValueTask DisposeAsync()
    {
        if (_browser != null) await _browser.DisposeAsync();
        _playwright?.Dispose();
        GC.SuppressFinalize(this);
    }
}

注册与修改Service

// Program.cs
builder.Services.Configure<ScreenshotOptions>(builder.Configuration.GetSection("ScreenshotOptions"));
builder.Services.AddSingleton<IPageFactory, PageFactory>();
builder.Services.AddSingleton<Service>();

// 修改后的Service类
public class Service
{
    private readonly ScreenshotOptions _options;
    private readonly IPageFactory _pageFactory;
    private IPage? _page;

    public Service(IOptions<ScreenshotOptions> options, IPageFactory pageFactory)
    {
        _options = options.Value;
        _pageFactory = pageFactory;
    }

    public async Task CreateScreenshotAsync()
    {
        _page ??= await _pageFactory.GetPageAsync();
        await _page.ScreenshotAsync(new PageScreenshotOptions { Path = _options.Filename });
        // 其他业务逻辑
    }
}

核心注意点

  • 资源释放:Playwright的IPlaywright、IBrowser必须正确释放,一定要实现IAsyncDisposable接口,防止资源泄漏。
  • 配置合法性:初始化前要确保ScreenshotOptions已正确绑定,避免空引用异常。
  • 生命周期一致性:IPage、IBrowser、IPlaywright都要注册为单例,重复创建会严重影响性能。

内容的提问来源于stack exchange,提问作者mu88

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 18:15:01