Skip to content

Commit 2ee02e6

Browse files
committed
Integrate with IIISEnvironmentFeature
This change adds support for IIISEnvironmentFeature, as well as a copy of it internally so we can have similar behavior within the adapters across all supported .NET versions and servers.
1 parent 4b112d8 commit 2ee02e6

File tree

5 files changed

+289
-5
lines changed

5 files changed

+289
-5
lines changed

global.json

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
11
{
22
"sdk": {
3-
"version": "8.0.100-rc.1.23455.8"
3+
"version": "8.0.100-rc.2.23502.2"
44
},
55
"tools": {
6-
"dotnet": "8.0.100-rc.1.23455.8",
6+
"dotnet": "8.0.100-rc.2.23502.2",
77
"runtimes": {
88
"dotnet": [
9-
"6.0.4"
9+
"6.0.4",
10+
"7.0.12"
1011
],
1112
"aspnetcore": [
12-
"6.0.4"
13+
"6.0.4",
14+
"7.0.12"
1315
]
1416
}
1517
},
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
#if !NET8_0_OR_GREATER
5+
using System;
6+
7+
namespace Microsoft.AspNetCore.Server.IIS;
8+
9+
/// <summary>
10+
/// This feature provides access to IIS application information. This is <see href="https://github.com/dotnet/aspnetcore/blob/4218bd758012820a955b0185e5b1824168d00c6a/src/Servers/IIS/IIS/src/IIISEnvironmentFeature.cs">available in-box</see>
11+
/// on .NET 8, but we have an internal copy so we can utilize its features on downlevel versions.
12+
/// </summary>
13+
internal interface IIISEnvironmentFeature
14+
{
15+
/// <summary>
16+
/// Gets the version of IIS that is being used.
17+
/// </summary>
18+
Version IISVersion { get; }
19+
20+
/// <summary>
21+
/// Gets the AppPool name that is currently running
22+
/// </summary>
23+
string AppPoolId { get; }
24+
25+
/// <summary>
26+
/// Gets the path to the AppPool config
27+
/// </summary>
28+
string AppPoolConfigFile { get; }
29+
30+
/// <summary>
31+
/// Gets path to the application configuration that is currently running
32+
/// </summary>
33+
string AppConfigPath { get; }
34+
35+
/// <summary>
36+
/// Gets the physical path of the application.
37+
/// </summary>
38+
string ApplicationPhysicalPath { get; }
39+
40+
/// <summary>
41+
/// Gets the virtual path of the application.
42+
/// </summary>
43+
string ApplicationVirtualPath { get; }
44+
45+
/// <summary>
46+
/// Gets ID of the current application.
47+
/// </summary>
48+
string ApplicationId { get; }
49+
50+
/// <summary>
51+
/// Gets the name of the current site.
52+
/// </summary>
53+
string SiteName { get; }
54+
55+
/// <summary>
56+
/// Gets the id of the current site.
57+
/// </summary>
58+
uint SiteId { get; }
59+
}
60+
#endif

src/Microsoft.AspNetCore.SystemWebAdapters.CoreServices/HostingRuntimeExtensions.cs

Lines changed: 92 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@
88
using Microsoft.AspNetCore.Hosting;
99
using Microsoft.Extensions.DependencyInjection;
1010
using Microsoft.Extensions.DependencyInjection.Extensions;
11+
using System.Diagnostics.CodeAnalysis;
12+
using Microsoft.AspNetCore.Server.IIS;
13+
using Microsoft.Extensions.Configuration;
14+
using Microsoft.AspNetCore.Hosting.Server;
1115

1216
namespace Microsoft.AspNetCore.SystemWebAdapters;
1317

@@ -21,6 +25,8 @@ public static void AddHostingRuntime(this IServiceCollection services)
2125
services.TryAddEnumerable(ServiceDescriptor.Singleton<IStartupFilter, HostingEnvironmentStartupFilter>());
2226

2327
services.AddOptions<SystemWebAdaptersOptions>()
28+
29+
// This configures for anyone using older IIS modules that don't set the values (and to maintain behavior with the adapters <1.3)
2430
.Configure(options =>
2531
{
2632
options.IsHosted = true;
@@ -32,6 +38,20 @@ public static void AddHostingRuntime(this IServiceCollection services)
3238
options.AppDomainAppVirtualPath = config.pwzVirtualApplicationPath;
3339
options.AppDomainAppPath = config.pwzFullApplicationPath;
3440
}
41+
})
42+
43+
// On .NET 8+, IIISEnvironmentFeature is available by default if running on IIS. We have an internal version
44+
// we load at startup so that regardless of version and server this may be available (for example, in case some
45+
// one wants to set the environment variables on a Kestrel hosted system to get the behavior)
46+
.Configure<IServer>((options, server) =>
47+
{
48+
if (server.Features.Get<IIISEnvironmentFeature>() is { } feature)
49+
{
50+
options.AppDomainAppPath = feature.ApplicationPhysicalPath;
51+
options.AppDomainAppVirtualPath = feature.ApplicationVirtualPath;
52+
options.ApplicationID = feature.ApplicationId;
53+
options.SiteName = feature.SiteName;
54+
}
3555
});
3656
}
3757

@@ -43,11 +63,82 @@ public HostingEnvironmentStartupFilter(HostingEnvironmentAccessor accessor)
4363
}
4464

4565
public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next)
46-
=> builder => next(builder);
66+
=> builder =>
67+
{
68+
// We ensure this feature is available for both pre-.NET 8 as well as .NET 8+ on non-IIS systems if the right environment variables are set
69+
if (builder.ApplicationServices.GetService<IServer>() is { } server && server.Features.Get<IIISEnvironmentFeature>() is null)
70+
{
71+
if (IISEnvironmentFeature.TryCreate(builder.ApplicationServices.GetRequiredService<IConfiguration>(), out var feature))
72+
{
73+
server.Features.Set<IIISEnvironmentFeature>(feature);
74+
}
75+
}
76+
77+
next(builder);
78+
};
4779

4880
public void Dispose()
4981
{
5082
HostingEnvironmentAccessor.Current = null;
5183
}
5284
}
85+
86+
/// <summary>
87+
/// Copy of https://github.com/dotnet/aspnetcore/blob/4218bd758012820a955b0185e5b1824168d00c6a/src/Servers/IIS/IIS/src/Core/IISEnvironmentFeature.cs
88+
/// </summary>
89+
private sealed class IISEnvironmentFeature : IIISEnvironmentFeature
90+
{
91+
public static bool TryCreate(IConfiguration configuration, [NotNullWhen(true)] out IIISEnvironmentFeature? result)
92+
{
93+
var feature = new IISEnvironmentFeature(configuration);
94+
95+
if (feature.IISVersion is not null)
96+
{
97+
result = feature;
98+
return true;
99+
}
100+
101+
result = null;
102+
return false;
103+
}
104+
105+
private IISEnvironmentFeature(IConfiguration configuration)
106+
{
107+
if (Version.TryParse(configuration["IIS_VERSION"], out var version))
108+
{
109+
IISVersion = version;
110+
}
111+
112+
if (uint.TryParse(configuration["IIS_SITE_ID"], out var siteId))
113+
{
114+
SiteId = siteId;
115+
}
116+
117+
AppPoolId = configuration["IIS_APP_POOL_ID"] ?? string.Empty;
118+
AppPoolConfigFile = configuration["IIS_APP_POOL_CONFIG_FILE"] ?? string.Empty;
119+
AppConfigPath = configuration["IIS_APP_CONFIG_PATH"] ?? string.Empty;
120+
ApplicationPhysicalPath = configuration["IIS_PHYSICAL_PATH"] ?? string.Empty;
121+
ApplicationVirtualPath = configuration["IIS_APPLICATION_VIRTUAL_PATH"] ?? string.Empty;
122+
ApplicationId = configuration["IIS_APPLICATION_ID"] ?? string.Empty;
123+
SiteName = configuration["IIS_SITE_NAME"] ?? string.Empty;
124+
}
125+
126+
public Version IISVersion { get; } = null!;
127+
128+
public string AppPoolId { get; }
129+
130+
public string AppPoolConfigFile { get; }
131+
132+
public string AppConfigPath { get; }
133+
134+
public string ApplicationPhysicalPath { get; }
135+
136+
public string ApplicationVirtualPath { get; }
137+
138+
public string ApplicationId { get; }
139+
140+
public string SiteName { get; }
141+
142+
public uint SiteId { get; }
143+
}
53144
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Collections.Generic;
5+
using System.Threading.Tasks;
6+
using Microsoft.AspNetCore.Hosting;
7+
using Microsoft.AspNetCore.Server.IIS;
8+
using Microsoft.AspNetCore.TestHost;
9+
using Microsoft.Extensions.Configuration;
10+
using Microsoft.Extensions.DependencyInjection;
11+
using Microsoft.Extensions.Hosting;
12+
using Microsoft.Extensions.Options;
13+
using Moq;
14+
using Xunit;
15+
16+
namespace Microsoft.AspNetCore.SystemWebAdapters;
17+
18+
public class HttpRuntimeIntegrationTests : SelfHostedTestBase
19+
{
20+
private const string IIS_VERSION = "IIS_Version";
21+
private const string IIS_SITE_ID = "IIS_SITE_ID";
22+
private const string IIS_SITE_NAME = "IIS_SITE_NAME";
23+
private const string IIS_APPLICATION_VIRTUAL_PATH = "IIS_APPLICATION_VIRTUAL_PATH";
24+
private const string IIS_PHYSICAL_PATH = "IIS_PHYSICAL_PATH";
25+
private const string IIS_APPLICATION_ID = "IIS_APPLICATION_ID";
26+
private const string IIS_APP_CONFIG_PATH = "IIS_APP_CONFIG_PATH";
27+
private const string IIS_APP_POOL_CONFIG_FILE = "IIS_APP_POOL_CONFIG_FILE";
28+
private const string IIS_APP_POOL_ID = "IIS_APP_POOL_ID";
29+
30+
[Fact]
31+
public async Task ConfigureRuntimeViaConfig()
32+
{
33+
// Arrange
34+
using var host = await GetTestHost()
35+
.ConfigureAppConfiguration(config =>
36+
{
37+
config.AddInMemoryCollection(new Dictionary<string, string>
38+
{
39+
[IIS_VERSION] = "10.0",
40+
[IIS_SITE_ID] = "1",
41+
[IIS_APP_POOL_ID] = IIS_APP_POOL_ID,
42+
[IIS_APP_POOL_CONFIG_FILE] = IIS_APP_POOL_CONFIG_FILE,
43+
[IIS_APP_CONFIG_PATH] = IIS_APP_CONFIG_PATH,
44+
[IIS_PHYSICAL_PATH] = IIS_PHYSICAL_PATH,
45+
[IIS_APPLICATION_VIRTUAL_PATH] = IIS_APPLICATION_VIRTUAL_PATH,
46+
[IIS_APPLICATION_ID] = IIS_APPLICATION_ID,
47+
[IIS_SITE_NAME] = IIS_SITE_NAME,
48+
});
49+
})
50+
.StartAsync();
51+
52+
// Act
53+
var options = host.Services.GetRequiredService<IOptions<SystemWebAdaptersOptions>>().Value;
54+
55+
// Assert
56+
Assert.Equal(IIS_SITE_NAME, options.SiteName);
57+
Assert.Equal(IIS_APPLICATION_VIRTUAL_PATH, options.AppDomainAppVirtualPath);
58+
Assert.Equal(IIS_PHYSICAL_PATH, options.AppDomainAppPath);
59+
Assert.Equal(IIS_APPLICATION_ID, options.ApplicationID);
60+
Assert.True(options.IsHosted);
61+
}
62+
63+
[Fact]
64+
public async Task ConfigureRuntimeViaFeature()
65+
{
66+
// Arrange
67+
var feature = new Mock<IIISEnvironmentFeature>();
68+
69+
feature.Setup(f => f.SiteName).Returns(IIS_SITE_NAME);
70+
feature.Setup(f => f.ApplicationVirtualPath).Returns(IIS_APPLICATION_VIRTUAL_PATH);
71+
feature.Setup(f => f.ApplicationPhysicalPath).Returns(IIS_PHYSICAL_PATH);
72+
feature.Setup(f => f.ApplicationId).Returns(IIS_APPLICATION_ID);
73+
feature.Setup(f => f.AppConfigPath).Returns(IIS_APP_CONFIG_PATH);
74+
feature.Setup(f => f.AppPoolConfigFile).Returns(IIS_APP_POOL_CONFIG_FILE);
75+
feature.Setup(f => f.AppPoolId).Returns(IIS_APP_POOL_ID);
76+
77+
using var host = await GetTestHost()
78+
.StartAsync();
79+
80+
host.GetTestServer().Features.Set<IIISEnvironmentFeature>(feature.Object);
81+
82+
// Act
83+
var options = host.Services.GetRequiredService<IOptions<SystemWebAdaptersOptions>>().Value;
84+
85+
// Assert
86+
Assert.Equal(IIS_SITE_NAME, options.SiteName);
87+
Assert.Equal(IIS_APPLICATION_VIRTUAL_PATH, options.AppDomainAppVirtualPath);
88+
Assert.Equal(IIS_PHYSICAL_PATH, options.AppDomainAppPath);
89+
Assert.Equal(IIS_APPLICATION_ID, options.ApplicationID);
90+
Assert.True(options.IsHosted);
91+
}
92+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using Microsoft.AspNetCore.Hosting;
5+
using Microsoft.AspNetCore.TestHost;
6+
using Microsoft.Extensions.DependencyInjection;
7+
using Microsoft.Extensions.Hosting;
8+
using Xunit;
9+
10+
namespace Microsoft.AspNetCore.SystemWebAdapters;
11+
12+
[Collection(nameof(SelfHostedTests))]
13+
public class SelfHostedTestBase
14+
{
15+
/// <summary>
16+
/// This method starts up a host in the background that
17+
/// makes it possible to initialize <see cref="HttpRuntime"/>
18+
/// and <see cref="HostingEnvironment"/> with values needed
19+
/// for testing with the <paramref name="configure"/> option.
20+
/// </summary>
21+
/// <param name="configure">
22+
/// Configuration for the hosting and runtime options.
23+
/// </param>
24+
protected static IHostBuilder GetTestHost()
25+
=> new HostBuilder()
26+
.ConfigureWebHost(webBuilder =>
27+
{
28+
webBuilder
29+
.UseTestServer()
30+
.ConfigureServices(services =>
31+
{
32+
services.AddSystemWebAdapters();
33+
})
34+
.Configure(app =>
35+
{
36+
// No need to configure pipeline for tests
37+
});
38+
});
39+
}

0 commit comments

Comments
 (0)