-
Notifications
You must be signed in to change notification settings - Fork 10.5k
Description
Moved from #43631.
Background and Motivation
When an ASP.NET Core application is hosted in IIS, IIS gives it an application ID, the same way it gives every other application an application ID. This contains the site ID (numeric) and virtual directory path, and is already used in ASP.NET Core to initialize ANCM and I believe to determine the value of HttpRequest.PathBase.
The particular scenario I have at the moment is a web application that runs multiple instances on different domains from the same physical path on disk. The only way the application can differentiate between instances e.g. for additional config or cache directories) is the IIS site ID.
In .NET Framework / ASP.NET applications, this value is available via HttpRuntime.AppDomainAppId which reads it from the current AppDomain's data. In ASP.NET Core, this value is currently only available during the lifetime of a request via IServerVariablesFeature, but outside of a single request there is no API for it.
I believe that applications that are hosted in IIS and rely on this value outside of a request lifetime (e.g. during startup or during background work) should be able to be ported from ASP.NET / .NET Framework to ASP.NET Core on modern .NET without needing to completely rework any logic that uses the AppDomainAppId.
The value is, however, already known internally to the ASP.NET Core Module and can be reached via reflection and pointer arithmetic. Making it available to the rest of the application is a question of "how", not "if".
Proposed API
namespace Microsoft.AspNetCore.Hosting;
+public static class IISWebHostDefaults
+{
+ public static readonly string IISConfigPathKey = "iis:configpath";
+}This API will depend upon the following additional behavioural changes, though they are not direct changes to the public API surface:
WebHostBuilderIISExtensions.UseIISwill set this key viaIWebHostBuilder.UseSetting.Microsoft.AspNetCore.Server.IIS.Core.IISConfigurationDatawill get a newstringfield to transfer this value from the native IIS module to the managed ASP.NET Core runtime.http_get_application_propertieswill copy the config path from unmanaged code into the struct to be marshalled back to managed code.
Usage Examples
var builder = WebApplication.CreateBuilder(args);
var configPath = builder.Configuration[IISWebHostDefaults.ConfigPathKey]; // "/LM/W3SVC/3/ROOT"
var siteID = configPath.Split('/')[3];
builder.Configuration.AddJsonFile($"MyAppConfig-Site{siteID}.json");
var app = builder.Build();
app.Run();var builder = WebApplication.CreateBuilder(args);
var configPath = builder.Configuration[IISWebHostDefaults.ConfigPathKey];
builder.Services.AddSomeModule(options => options.Category = $"MyCategory-{configPath}");
var app = builder.Build();
app.Run();var builder = WebApplication.CreateBuilder(args);
app.AddSomeModule();
var app = builder.Build();
app.UseSomeModuleThatNeedsSiteID(app.Configuration[IISWebHostDefaults.ConfigPathKey]);
app.Run();class MyDependencyInjectedClass
{
public MyDependencyInjectedClass(IConfiguration configuration)
{
var siteSpecificDirectory = Sanitize(configuration[IISWebHostDefaults.ConfigPathKey]);
cacheDirectory = Path.Combine(Path.GetTempPath(), "MyApplication", siteSpecificDirectory );
}
readonly string cacheDirectory;
static string Sanitize(string input)
{
// imagine there's something cool here, its not relevant
}
}Alternative Designs
I did consider a method similar to .NET Framework of exposing a static function/property, as http_get_application_properties can be called from anywhere, however this does not easily allow the consuming code to be tested or for stub/mock values to be provided. Every consumer that wished to do so would immediately create their own interface and implementation to simply wrap this static call.
Risks
This will require changes to ANCM, and I do not know what the forwards-compatibility and backwards-compatibility concerns here are and how to mitigate them.
Looking at the most recent change which added a field to the IISConfigurationData struct, there appear to be no compatibility mitigations / versioning / traditional dwSize fields etc., so this may not be a concern?
Additional Notes
This can be done already, albeit extremely hackily, by reflection and pointer arithmetic to access values stored within the IIS integration memory:
var builder = WebApplication.CreateBuilder(args);
var nativeApplicationType = Type.GetType("Microsoft.AspNetCore.Server.IIS.Core.IISNativeApplication, Microsoft.AspNetCore.Server.IIS");
var iisNativeApplication = builder.Services.Where(s => s.ServiceType == nativeApplicationType)
.Select(x => x.ImplementationInstance)
.FirstOrDefault();
if (nativeApplicationType?.GetField("_nativeApplication", BindingFlags.NonPublic | BindingFlags.Instance) is { } field &&
field.GetValue(iisNativeApplication) is SafeHandle nativeApplicationHandle)
{
var nativeApplicationPointer = nativeApplicationHandle.DangerousGetHandle();
var objectPointer = IntPtr.Add(nativeApplicationPointer, 136); // Assuming x64, recalculate for x86
var configPathPointer = Marshal.ReadIntPtr(objectPointer);
// Got the config path!
var configPath = Marshal.PtrToStringUni(configPathPointer);
}I would much rather this be exposed as an API as this relies heavily on not just ASP.NET internals but also the memory layout of underlying native types.
I am happy to drive the implementation myself with a PR, once a design is approved.