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

ASP.Net 6迁移后自定义WebApplicationFactory抛出异常如何解决

解决方法

问题根因

ASP.NET 6 引入的最小宿主模型(WebApplication/WebApplicationBuilder)对宿主配置的修改做了限制:一旦WebApplicationBuilder开始构建,就不允许再通过IWebHostBuilder修改ContentRoot、环境变量这类基础宿主配置。WebApplicationFactory默认运行时会尝试将测试项目的输出目录设为ContentRoot,和主项目的原有配置冲突就会抛出该异常。

解决步骤

1. 调整主项目Program.cs的构建逻辑

将原有的直接传参创建Builder的逻辑,改为显式传入WebApplicationOptions,允许外部传入配置参数:

// 原代码
// var builder = WebApplication.CreateBuilder(args);

// 修改为
var builder = WebApplication.CreateBuilder(new WebApplicationOptions
{
    Args = args,
    // 运行时优先使用传入的ContentRoot,没有的话用当前目录
    ContentRootPath = string.IsNullOrEmpty(Environment.GetEnvironmentVariable("ASPNETCORE_CONTENTROOT")) 
        ? Directory.GetCurrentDirectory() 
        : Environment.GetEnvironmentVariable("ASPNETCORE_CONTENTROOT")
});

如果你的Program类是顶级语句没有显式类声明,在Program.cs的末尾加上这行,方便测试项目引用:

public partial class Program { }

2. 调整CustomWebApplicationFactory的实现

  • 不要直接修改全局环境变量,改用IWebHostBuilder提供的配置方法
  • 重写GetContentRootDir方法手动指定主项目的ContentRoot,避免WebApplicationFactory自动修改触发冲突
  • 泛型参数改为Program类(如果用了最小API没有Startup类的话,原有Startup类的场景可以保留原来的泛型约束)

修改后的工厂代码示例:

public class CustomWebApplicationFactory<TStartup> : WebApplicationFactory<TStartup> where TStartup : class
{
    protected override void ConfigureWebHost(IWebHostBuilder builder)
    {
        // 改用UseEnvironment设置测试环境,不要修改全局环境变量
        builder.UseEnvironment("IntegrationTests");
        
        builder.ConfigureServices(services => {
            // Create a new service provider.
            var serviceProvider = new ServiceCollection()
                .AddEntityFrameworkInMemoryDatabase().AddLogging()
                .BuildServiceProvider();

            // Add a database context (AppDbContext) using an in-memory database for testing.
            services.AddDbContextPool<AppDbContext>(options =>
            {
                options.UseInMemoryDatabase("InMemoryAppDb");
                options.UseInternalServiceProvider(serviceProvider);
                options.EnableSensitiveDataLogging();
                options.EnableDetailedErrors();
                options.LogTo(Console.WriteLine);
            });

            services.AddDbContextPool<AppIdentityDbContext>(options =>
            {
                options.UseInMemoryDatabase("InMemoryIdentityDb");
                options.UseInternalServiceProvider(serviceProvider);
                options.EnableSensitiveDataLogging();
                options.EnableDetailedErrors();
                options.LogTo(Console.WriteLine);
            });
            services.AddScoped<SignInManager<AppUser>>();
            services.AddScoped<ILogger<UserRepository>>(provider => {
                ILoggerFactory loggerFactory = provider.GetRequiredService<ILoggerFactory>();
                return loggerFactory.CreateLogger<UserRepository>();
            });
            services.AddDistributedMemoryCache();
            // Build the service provider.
            var sp = services.BuildServiceProvider();

            // Create a scope to obtain a reference to the database contexts
            using (var scope = sp.CreateScope())
            {
                var scopedServices = scope.ServiceProvider;
                var appDb = scopedServices.GetRequiredService<AppDbContext>();
                var identityDb = scopedServices.GetRequiredService<AppIdentityDbContext>();
                var logger = scopedServices.GetRequiredService<ILogger<CustomWebApplicationFactory<TStartup>>>();

                // Ensure the database is created.
                appDb.Database.EnsureCreated();
                identityDb.Database.EnsureCreated();

                try
                {
                    // Seed the database with test data.
                    SeedData.PopulateTestData(identityDb);
                    SeedData.PopulateTestData(appDb);
                }
                catch (Exception ex)
                {
                    logger.LogError(ex, $"An error occurred seeding the " +
                        $"database with test messages. Error: {ex.Message}");
                }
            }
        });
    }

    // 显式指定主项目的ContentRoot路径,避免自动切换触发异常
    protected override string GetContentRootDir(string contentRootFileLocatorPath)
    {
        // 按照你实际的项目目录结构调整相对路径,示例为主项目在src/Web.Api、测试项目在test/Web.Api.IntegrationTests的场景
        return Path.GetFullPath(Path.Combine(base.GetContentRootDir(contentRootFileLocatorPath), "../../../../src/Web.Api"));
    }
}

3. 备用兼容方案

如果上述修改后仍有冲突,可以重写WebApplicationFactory的CreateHost方法,完全手动控制宿主构建逻辑:

protected override IHost CreateHost(IHostBuilder builder)
{
    builder.UseContentRoot(Path.GetFullPath("../../../../src/Web.Api"));
    builder.UseEnvironment("IntegrationTests");
    return base.CreateHost(builder);
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.26 14:54:05