Skip to content

Regression: Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory no longer allows the content root to be set programatically in some cases #34354

@JeremySkinner

Description

@JeremySkinner

Describe the bug

Hi, In .NET 5 it was possible to override the ConfigureWebHost method of WebApplicationFactory and call builder.UseContentRoot(".") when the content root can't be inferred (eg if the startup class isn't defined inside a web project).

In .NET 6, this is no longer possible as the SetContentRoot method now always calls GetContentRootFromFile. GetContentRootFromFile throws an exception rather than returning null if no content root is present in MvcTestingAppManifest.json.

This affects the integration tests of the FluentValidation project, which need to be able to set the content root programatically (the controller end points, startup class, and integration tests themselves are all in the same project).

I haven't looked into how the MvcTestingAppManifest.json file is generated, but it seems that it isn't populated with the test assembly itself, which is a problem if the Startup/entrypoint class is defined inside the test project, rather than a web project.

To Reproduce

  • Create an integration test project
  • Add the startup file to the integration test project (not to a separate web project)
  • Create the test host using WebApplicationFactory to point to the Startup class that's inside the test project
  • edit I've made a small sample project to reproduce: https://github.com/JeremySkinner/IntegrationTestingRegression

Here is the web application factory that I was using in .net 5:

public class WebAppFixture : WebApplicationFactory<Startup> {

		protected override void ConfigureWebHost(IWebHostBuilder builder) {
			builder.UseContentRoot(".");
		}
 //...
}

In .NET 5 this worked fine, but in .NET 6 ConfigureWebHost is never called as GetContentRootFromFile will always throw an exception before it gets to this point.

My hacky workaround is to delete the manfiest file in the ctor:

public WebAppFixture() {
	if (File.Exists("MvcTestingAppManifest.json")) {
		File.Delete("MvcTestingAppManifest.json");
	}
}

...and then GetContentRootFromFile will never execute, and the exception won't be thrown.

Recommendation

If GetContentRootFromFile could be modified to return null rather than throw an exception, then the previous behaviour would continue to work.

private static string GetContentRootFromFile(string file)
{
var data = JsonSerializer.Deserialize<IDictionary<string, string>>(File.ReadAllBytes(file))!;
var key = typeof(TEntryPoint).Assembly.GetName().FullName;
if (!data.TryGetValue(key, out var contentRoot))
{
throw new KeyNotFoundException($"Could not find content root for project '{key}' in test manifest file '{file}'");
}
return (contentRoot == "~") ? AppContext.BaseDirectory : contentRoot;
}

If the above method returned null, then the "old" (net5) codepath would continue to run, as it already handles the case when the content root is null:

private void SetContentRoot(IWebHostBuilder builder)
{
if (SetContentRootFromSetting(builder))
{
return;
}
var fromFile = File.Exists("MvcTestingAppManifest.json");
var contentRoot = fromFile ? GetContentRootFromFile("MvcTestingAppManifest.json") : GetContentRootFromAssembly();
if (contentRoot != null)
{
builder.UseContentRoot(contentRoot);
}
else
{
builder.UseSolutionRelativeContentRoot(typeof(TEntryPoint).Assembly.GetName().Name!);
}
}

...and I believe adding an override in ConfigureWebHost would then continue to work.

Exceptions (if any)

Error Message:
   System.Collections.Generic.KeyNotFoundException : Could not find content root for project 'FluentValidation.Tests.AspNetCore, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null' in test manifest file 'MvcTestingAppManifest.json'

Further technical details

  • ASP.NET Core version: 6 preview 6
  • Include the output of dotnet --info
.NET SDK (reflecting any global.json):
 Version:   6.0.100-preview.6.21355.2
 Commit:    7f8e0d76c0

Runtime Environment:
 OS Name:     ubuntu
 OS Version:  20.04
 OS Platform: Linux
 RID:         ubuntu.20.04-x64
 Base Path:   /home/jskinner/.dotnet/sdk/6.0.100-preview.6.21355.2/

Host (useful for support):
  Version: 6.0.0-preview.6.21352.12
  Commit:  770d630b28

.NET SDKs installed:
  2.1.505 [/home/jskinner/.dotnet/sdk]
  3.1.408 [/home/jskinner/.dotnet/sdk]
  5.0.202 [/home/jskinner/.dotnet/sdk]
  6.0.100-preview.6.21355.2 [/home/jskinner/.dotnet/sdk]

.NET runtimes installed:
  Microsoft.AspNetCore.All 2.1.9 [/home/jskinner/.dotnet/shared/Microsoft.AspNetCore.All]
  Microsoft.AspNetCore.App 2.1.9 [/home/jskinner/.dotnet/shared/Microsoft.AspNetCore.App]
  Microsoft.AspNetCore.App 3.1.14 [/home/jskinner/.dotnet/shared/Microsoft.AspNetCore.App]
  Microsoft.AspNetCore.App 5.0.5 [/home/jskinner/.dotnet/shared/Microsoft.AspNetCore.App]
  Microsoft.AspNetCore.App 6.0.0-preview.6.21355.2 [/home/jskinner/.dotnet/shared/Microsoft.AspNetCore.App]
  Microsoft.NETCore.App 2.1.9 [/home/jskinner/.dotnet/shared/Microsoft.NETCore.App]
  Microsoft.NETCore.App 3.1.14 [/home/jskinner/.dotnet/shared/Microsoft.NETCore.App]
  Microsoft.NETCore.App 5.0.5 [/home/jskinner/.dotnet/shared/Microsoft.NETCore.App]
  Microsoft.NETCore.App 6.0.0-preview.6.21352.12 [/home/jskinner/.dotnet/shared/Microsoft.NETCore.App]

To install additional .NET runtimes or SDKs:
  https://aka.ms/dotnet-download


Metadata

Metadata

Assignees

Labels

area-mvcIncludes: MVC, Actions and Controllers, Localization, CORS, most templatesfeature-mvc-testingMVC testing package

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions