Open bachratyg opened 5 years ago
While your observations make sense, I will challenge the expectations a little.
A) PathBase is not a stable value, it can change several times during the processing of a request. Components like Map, UsePathBase, and Localization can all alter it. B) OnStarting is not guaranteed a consistent view of the request since it can fire at very different points in the lifecycle as you observed. OnStarting's guarantees focus only on the response.
Yes, you'd get more consistent OnStarting behavior for IIS in-process if the path base was maintained in the server, but those other components would still adjust it. There's not much that could be done for IIS out-of-process since the integration only happens in middleware.
Recommendation: Consider handling IIS in-process PathBase directly in the server like HttpSys does. It would be more efficient and provide a more consistent PathBase experience across the lifespan of the request. Priority: low.
PathBase being stable or not is not the issue here. There is deliberately no middleware in the pipeline on my part that could affect PathBase. Since IIS integration is configured on the host and the application base is dictated by IIS I would expect that the context is fully initialized by the time it enters the pipeline and is not "de-initialized" before leaving it. Note: I consider Response.OnStarting and Response.OnCompleted as part of the pipeline since callbacks are usually registered from middlewares.
As a workaround would it be reasonable to query the IIS environment directly like this instead of relying on HttpContext.PathBase?
var pathBase = config.GetSetting("APPL_PATH")
?? Environment.GetEnvironmentVariable($"ASPNETCORE_APPL_PATH");
as in src/Microsoft.AspNetCore.Server.IISIntegration/WebHostBuilderIISExtensions.cs?
Maybe also consider exposing hosting-related properties on IWebHostEnvironment
?
Couldn't you capture PathBase at the same time as you register OnStarting?
Maybe also consider exposing hosting-related properties on
IWebHostEnvironment
?
We don't consider PathBase a hosting config, but rather a server config. Also note that the support for PathBase is different across all four supported servers. Kestrel has no support, HttpSys supports multiple, IIS out-of-proc supports one via middleware, and IIS in-proc supports one (via middleware?).
My repro is somewhat simplified, but the following should possibly work:
private static readonly object ApplicationBaseMarker = "ApplicationBase";
// Call this early in the pipeline/from IStartupFilter
public static IApplicationBuilder MarkApplicationBase(this IApplicationBuilder builder)
{
return builder.Use(next => context =>
{
context.Items[ApplicationBaseMarker] = context.Request.PathBase;
return next(context);
});
}
// Use this instead of PathBase
public static PathString GetApplicationBase(this HttpContext context)
{
return (PathString)context.Items[ApplicationBaseMarker];
}
as long as it's only my code and no external deps are triggered in OnStarting that would use the PathBase.
Edit: this would not work when using things like IUrlHelper.Link
.
We don't consider PathBase a hosting config, but rather a server config.
Then maybe expose it on a feature interface similar to IServerAddressesFeature
/IServerVariablesFeature
?
Also note that the support for PathBase is different across all four supported servers. Kestrel has no support, HttpSys supports multiple, IIS out-of-proc supports one via middleware, and IIS in-proc supports one (via middleware?).
When the server supports multiple path bases, one is the prefix of the other and both are valid prefixes for a request then which would be chosen?
When the server supports multiple path bases, one is the prefix of the other and both are valid prefixes for a request then which would be chosen?
In HttpSys you can register multiple unrelated prefixes and the most specific match wins. E.g. http://localhost:80/a https://localhost:443/a/b/c http://myhost:80/a/b https://myhost:443/a/b/c/d
My recommendation is still the same as above and should address this for you for in-process:
Recommendation: Consider handling IIS in-process PathBase directly in the server like HttpSys does. It would be more efficient and provide a more consistent PathBase experience across the lifespan of the request. Priority: low.
This is worth fixing, but I'm not sure it's worth patching.
Describe the bug
Response.OnStarting does not observe the same PathBase as the rest of the pipeline on empty-body responses.
To Reproduce
launchSettings.json
Program.cs
Startup.cs
Project.csproj (2.1)
Project.csproj (3.0)
Run the app, run any request, then observe the response headers. The PathBase observed in the OnStarting callback is empty.
expected headers
X-PATHBASE1: >>>/NFE X-PATHBASE2: >>>/NFE
actual headers
X-PATHBASE1: >>>/NFE X-PATHBASE2: >>>
actual headers when using response body e.g.
app.Run(context => context.Response.WriteAsync(""));
X-PATHBASE1: >>>/NFE X-PATHBASE2: >>>/NFE
Additional remarks
The root of the problem seems to be how IIS integration is imlemented. IISSetupFilter/IISServerSetupFilter injects the UsePathBase middleware early in the pipeline which sets the proper PathBase from IIS metadata. However if there is no response body then OnStarting executes after all middlewares have completed and at this point the UsePathBaseMiddleware have reverted the PathBase to its original empty value.
Possibly related: #5898
Further technical details
both 2.1 and 3.0 seems to be affected
dotnet --info
.NET Core SDK (reflecting any global.json): Version: 3.0.100 Commit: 04339c3a26
Runtime Environment: OS Name: Windows OS Version: 10.0.18362 OS Platform: Windows RID: win10-x64 Base Path: C:\Program Files\dotnet\sdk\3.0.100\
Host (useful for support): Version: 3.0.0 Commit: 7d57652f33
.NET Core SDKs installed: 2.1.802 [C:\Program Files\dotnet\sdk] 2.2.402 [C:\Program Files\dotnet\sdk] 3.0.100 [C:\Program Files\dotnet\sdk]
.NET Core runtimes installed: Microsoft.AspNetCore.All 2.1.13 [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.13 [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.0.0 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.NETCore.App 2.1.13 [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.0.0 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.WindowsDesktop.App 3.0.0 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Visual Studio Community 2019 (16.3.8)