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
相关产品推荐
相关产品推荐

