aspnetcore: Kestrel Web Server returns 404 to all requests while using inherited Startup from another project (application, assembly). Net Core 3.1.
Description of the bug:
Link to repository with solution: https://github.com/AnastasiyaPoli/MemberService
Short description of the solution: there are two projects in solution: web project MemberService.Api and tests project MemberService.SystemTests. MemberService.Api starts Kestrel service and has a controller DiagnosticController with endpoint “ping”. MemberService.SystemTests has a test to test the first project. It starts Kestrel service, uses a proxy and calls endpoint “ping”.
The problem is: in MemberService.SystemTests I have to use TestStartup (declared in file TestStartup.cs in project MemberService.SystemTests), that inherits Startup from project MemberService.Api, because some of settings should be changed for tests (for example, if I am using Application Insights in my project, I will not use them for tests). Configuring Kestrel server, that uses inherited TestStartup was a big problem.
In file KestrelWebService.cs (line 17) there is such code now for service configuration:
Server = new WebHostBuilder()
.UseKestrel()
.UseStartup<TestStartup>()
.ConfigureKestrel((context, options) =>
{
options.Listen(IPAddress.Loopback, 5000);
})
.UseConfiguration(Configuration)
.UseSetting("applicationName", "MemberService.Api")
.Build();
In this way the test Given_ServiceIsRunning_When_PingActionIsCalled_Then_ResponseStatusCodeShouldBeSucceed in file DiagnosticTests.cs works fine. But the line UseSetting("applicationName", "MemberService.Api") is a hack to change assembly (application) name after building a server. Without it application name “MemberService.SystemTests” is used and the test fails: “ping” call to server is not 200 (OK), but 404 (NotFound).
Steps to reproduce
- Run test Given_ServiceIsRunning_When_PingActionIsCalled_Then_ResponseStatusCodeShouldBeSucceed in file DiagnosticTests.cs. You will get successful result.
- Comment line
.UseSetting("applicationName", "MemberService.Api")(line 28) in file KestrelWebService.cs. - Run test Given_ServiceIsRunning_When_PingActionIsCalled_Then_ResponseStatusCodeShouldBeSucceed in file DiagnosticTests.cs again. You will get failure result. “Ping” request to server will return 404 (NotFound). You can see it, setting a breakpoint on line
return Result.ParseFromHttpResponse(response);(line 12) in file HttpClientExtensions.cs and look at response variable.
Test is also successful in such case:
- Comment line
.UseSetting("applicationName", "MemberService.Api")(line 28) in file KestrelWebService.cs. - In file KestrelWebService.cs in line
.UseStartup<TestStartup>()(line 22) change type from TestStartup to TestStartupApi (declared in file Startup.cs in project MemberService.Api). - Run test Given_ServiceIsRunning_When_PingActionIsCalled_Then_ResponseStatusCodeShouldBeSucceed in file DiagnosticTests.cs. You will get successful result.
Resolution: classes TestStartupApi and TestStartup are completely the same, but located in different projects (applications, assemblies). With TestStartupApi Kestrel service works fine (“ping” call is successful) but with TestStartup - it is not working (“ping” call is failing). Changing assembly (application) name from “MemberService.SystemTests” to “MemberService.Api” after building a server helps, but it does not seem like normal behavior.
Important note: the code worked for .NET Core 2.2, but caused problems for .NET Core 3.1. The issue became visible after changing the version.
Could you suggest a correct solution to configure Kestrel service for project MemberService.SystemTests in this case? Or is it a bug that is going to be fixed? Thanks in advance!
Exceptions:
The problem is not followed by any exceptions.
Further technical details
- ASP.NET Core version: 3.1
- the output of
dotnet --info:
.NET Core SDK (reflecting any global.json):
Version: 3.1.202
Commit: 6ea70c8dca
Runtime Environment:
OS Name: Windows
OS Version: 10.0.17134
OS Platform: Windows
RID: win10-x64
Base Path: C:\Program Files\dotnet\sdk\3.1.202\
Host (useful for support):
Version: 3.1.4
Commit: 0c2e69caa6
.NET Core SDKs installed:
2.1.200 [C:\Program Files\dotnet\sdk]
2.1.400 [C:\Program Files\dotnet\sdk]
2.1.402 [C:\Program Files\dotnet\sdk]
2.1.403 [C:\Program Files\dotnet\sdk]
2.1.514 [C:\Program Files\dotnet\sdk]
2.1.602 [C:\Program Files\dotnet\sdk]
2.2.203 [C:\Program Files\dotnet\sdk]
3.1.202 [C:\Program Files\dotnet\sdk]
.NET Core runtimes installed:
Microsoft.AspNetCore.All 2.1.2 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
Microsoft.AspNetCore.All 2.1.4 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
Microsoft.AspNetCore.All 2.1.5 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
Microsoft.AspNetCore.All 2.1.9 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
Microsoft.AspNetCore.All 2.1.18 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
Microsoft.AspNetCore.All 2.2.4 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
Microsoft.AspNetCore.All 2.2.7 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
Microsoft.AspNetCore.App 2.1.2 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 2.1.4 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 2.1.5 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 2.1.9 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 2.1.18 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 2.2.4 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 2.2.7 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 3.1.4 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.NETCore.App 2.0.7 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 2.1.2 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 2.1.4 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 2.1.5 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 2.1.9 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 2.1.18 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 2.2.4 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 2.2.7 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 3.1.4 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.WindowsDesktop.App 3.1.4 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
To install additional .NET Core runtimes or SDKs: https://aka.ms/dotnet-download
- The IDE: Visual Studio Professional 2019, version: 16.5.5
About this issue
- Original URL
- State: closed
- Created 4 years ago
- Comments: 19 (9 by maintainers)
@davidfowl thanks for the solution and explanations. It really works if
<EnableDefaultContentItems>false</EnableDefaultContentItems>added to .csproj file (at least, in my case).Checked once again the documentation https://docs.microsoft.com/en-us/dotnet/core/compatibility/2.2-3.1 and did not find information about that change. It would be just great if it could be added, because the behavior is not obvious at all when the error occurs.
@AnastasiyaPoli I’ll take a look at your repos today.
@davidfowl, @Tratcher Would you agree that it’s unobvious that while resolving controllers MVC relies on some “applicationName” setting and expects that ‘string’ setting to correspond to some assembly name where controllers are located? Documentation suggests to set applicationName to the entry-point assembly which leads to an issue in described scenario. If overriding that setting manually as Anastasiya did is not a hack - it’s an ‘Inappropriate Intimacy’ code smell at least. So does MVC, assuming controllers are located in same assembly as Startup file and not even providing easy way of changing that.
Another interesting question to think of: what if someone have 2 assemblies with controllers? Then first assembly must be specified via ‘applicationName’ and second - via AddApplicationParts method - completely different approaches for doing the same thing.
I can see at least a few ways how this could be made more easy to understand:
IWebHostBuilder:UseWebAppAssembly(Assembly a)which may internally set the setting ‘applicationName’. This is non-breaking change and extreemely easy to introduce.TStartupinUseStartup<TStartup>()method and add all those root type assemblies as application parts. May be breaking-change, but really unlikely someone gets some controllers loaded which shouldn’t have been loaded anyway.AddApplicationPartscall required and do not rely on unverified assumptions (such as controllers are located in the same assembly as Startup class). It’s so hard to debug such implicit default behaviors which do not indicate any errors! I faced it myself couple of times with Azure Functions and AppInsights before…MVC could throw on startup if it doesn’t discover any controllers. Then at least the issue would be more obvious.
That’s because there’s no reasonable way to know what’s wrong. Controllers can be anywhere, in the main app, in the assembly where the startup class is or in another assembly altogether.
That said, what we have now is a pit of failure and we’ve seen this 404 problem come up lots of times. I think as a minimum bar we can add verbose logging (I though we did that already …)
cc @pranavkm