RayWangQvQ/BiliBiliToolPro · critical · InvalidOperationException

IConfigurationRoot not available — cannot access Providers…

Error message

IConfigurationRoot not available — cannot access Providers or Reload()

What it means

BiliAccountPageWorkflow's primary constructor casts the injected IConfiguration to IConfigurationRoot and throws InvalidOperationException('IConfigurationRoot not available — cannot access Providers or Reload()') when the cast fails. The workflow needs IConfigurationRoot to enumerate providers and trigger Reload() after mutating account cookies; a plain IConfiguration wrapper (e.g. a custom implementation, Options-backed, or test double) cannot support that.

Solutions

  1. Register the real host configuration root in DI: services.AddSingleton<IConfiguration>(hostBuilder.Configuration) where hostBuilder.Configuration is the IConfigurationRoot.
  2. In tests, build a real ConfigurationRoot (new ConfigurationBuilder().AddInMemoryCollection(...) .Build()) instead of mocking IConfiguration.
  3. Ensure the service resolves the root, not a section — do not pass Configuration.GetSection(...).
  4. If a wrapper is unavoidable, implement IConfigurationRoot (expose Providers and Reload()) in the wrapper.

Example fix

// before (test)
services.AddSingleton<IConfiguration>(new ConfigurationBuilder().Build()); // not a Root? actually Build() returns Root — wrong case: custom wrapper
services.AddSingleton<IConfiguration>(new FakeConfiguration());
// after
var root = new ConfigurationBuilder().AddInMemoryCollection(new Dictionary<string,string>{["BiliBiliCookies__0"]="SESSDATA=x"}).Build();
services.AddSingleton<IConfiguration>(root); // root implements IConfigurationRoot
Defensive patterns

Strategy: validation

Validate before calling

// before constructing the workflow
var root = configuration as IConfigurationRoot;
if (root is null)
    throw new InvalidOperationException("DI必须注册真正的IConfigurationRoot(hostBuilder.Configuration)");

Try / catch

try { workflow = new BiliAccountPageWorkflow(configuration, loginSvc); }
catch (InvalidOperationException ex)
{
    logger.LogError(ex, "BiliAccountPageWorkflow初始化失败:配置根不可用");
    throw;
}

Prevention

When it happens

Trigger: DI registration supplies an IConfiguration implementation that is not the host's ConfigurationRoot (custom IConfiguration wrapper, configuration section passed instead of the root, or a test fake) so 'configuration as IConfigurationRoot' evaluates to null.

Common situations: Unit/integration tests registering a mock IConfiguration; refactored hosting code that registers a custom configuration implementation; injecting a configuration section rather than the root into the workflow.

Understand the failure class

Background: "is not a compatible type" / "cannot merge" errors: when a value's type doesn't match what the library requires — this error's family across 65 libraries.

Related errors


AI-assisted analysis of RayWangQvQ/BiliBiliToolPro@c599b2c0da (2026-09-12). Data as JSON: /api/errors/331e5f88a0e99b8e. Report an issue: GitHub.

Appendix: source

Thrown at src/Ray.BiliBiliTool.Web/Services/Pages/BiliAccount/BiliAccountPageWorkflow.cs:16

using Microsoft.Extensions.Configuration;
using Ray.BiliBiliTool.Agent;
using Ray.BiliBiliTool.Config.SQLite;
using Ray.BiliBiliTool.DomainService.Dtos;
using Ray.BiliBiliTool.DomainService.Interfaces;

namespace Ray.BiliBiliTool.Web.Services.Pages.BiliAccount;

public class BiliAccountPageWorkflow(
    IConfiguration configuration,
    ILoginDomainService loginDomainService
) : IBiliAccountPageWorkflow
{
    private readonly IConfigurationRoot _configurationRoot =
        configuration as IConfigurationRoot
        ?? throw new InvalidOperationException(
            "IConfigurationRoot not available — cannot access Providers or Reload()"
        );

    public Task<List<BiliAccountDto>> GetAllAccountsAsync()
    {
        var cookieList = _configurationRoot.GetSection("BiliBiliCookies").Get<List<string>>() ?? [];
        var accounts = new List<BiliAccountDto>();

        for (int i = 0; i < cookieList.Count; i++)
        {
            var cookieStr = cookieList[i];
            var userId = ParseUserId(cookieStr);
            accounts.Add(new BiliAccountDto(i, userId, cookieStr));
        }

        return Task.FromResult(accounts);
    }

View on GitHub (pinned to c599b2c0da)