diff --git a/.appveyor.yml b/.appveyor.yml
index 5a38c39fa9..c8a63feb48 100644
--- a/.appveyor.yml
+++ b/.appveyor.yml
@@ -6,6 +6,8 @@ branches:
- release
- dev
- /^(.*\/)?ci-.*$/
+install:
+ - ps: .\tools\update_schema.ps1
build_script:
- ps: .\run.ps1 default-build
clone_depth: 1
@@ -15,4 +17,4 @@ environment:
DOTNET_CLI_TELEMETRY_OPTOUT: 1
test: off
deploy: off
-os: Visual Studio 2017
\ No newline at end of file
+os: Visual Studio 2017
diff --git a/IISIntegration.sln b/IISIntegration.sln
index e861a3045f..41411cfd76 100644
--- a/IISIntegration.sln
+++ b/IISIntegration.sln
@@ -1,7 +1,7 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
-VisualStudioVersion = 15.0.27130.2020
+VisualStudioVersion = 15.0.27130.2026
MinimumVisualStudioVersion = 15.0.26730.03
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{04B1EDB6-E967-4D25-89B9-E6F8304038CD}"
ProjectSection(SolutionItems) = preProject
diff --git a/samples/NativeIISSample/NativeIISSample.csproj b/samples/NativeIISSample/NativeIISSample.csproj
index daf174a0d4..d9c2419b8f 100644
--- a/samples/NativeIISSample/NativeIISSample.csproj
+++ b/samples/NativeIISSample/NativeIISSample.csproj
@@ -9,13 +9,13 @@
-
-
+
+
-
-
+
+
-
+
diff --git a/samples/NativeIISSample/Startup.cs b/samples/NativeIISSample/Startup.cs
index ef7bbad8ce..3fac22e9da 100644
--- a/samples/NativeIISSample/Startup.cs
+++ b/samples/NativeIISSample/Startup.cs
@@ -7,19 +7,20 @@ using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Server.IIS;
using Microsoft.AspNetCore.Server.IISIntegration;
namespace NativeIISSample
{
public class Startup
{
-
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, IAuthenticationSchemeProvider authSchemeProvider)
{
app.Run(async (context) =>
{
context.Response.ContentType = "text/plain";
+
await context.Response.WriteAsync("Hello World - " + DateTimeOffset.Now + Environment.NewLine);
await context.Response.WriteAsync(Environment.NewLine);
@@ -60,9 +61,29 @@ namespace NativeIISSample
await context.Response.WriteAsync(key + ": " + value + Environment.NewLine);
}
await context.Response.WriteAsync(Environment.NewLine);
+
+ // accessing IIS server variables
+ await context.Response.WriteAsync("Server Variables:" + Environment.NewLine);
+
+ foreach (var varName in IISServerVarNames)
+ {
+ await context.Response.WriteAsync(varName + ": " + context.GetIISServerVariable(varName) + Environment.NewLine);
+ }
});
}
+ private static readonly string[] IISServerVarNames =
+ {
+ "AUTH_TYPE",
+ "AUTH_USER",
+ "CONTENT_TYPE",
+ "HTTP_HOST",
+ "HTTPS",
+ "REMOTE_PORT",
+ "REMOTE_USER",
+ "REQUEST_METHOD",
+ };
+
public static void Main(string[] args)
{
var host = new WebHostBuilder()
diff --git a/src/Microsoft.AspNetCore.Server.IISIntegration/IISHttpContextExtensions.cs b/src/Microsoft.AspNetCore.Server.IISIntegration/IISHttpContextExtensions.cs
new file mode 100644
index 0000000000..fd72e9d00f
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Server.IISIntegration/IISHttpContextExtensions.cs
@@ -0,0 +1,38 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Http.Features;
+
+namespace Microsoft.AspNetCore.Server.IIS
+{
+ ///
+ /// Extensions to that enable access to IIS features.
+ ///
+ public static class IISHttpContextExtensions
+ {
+ ///
+ /// Gets the value of a server variable for the current request.
+ ///
+ /// The http context for the request.
+ /// The name of the variable.
+ ///
+ /// null if the feature does not support the feature.
+ /// May return null or empty if the variable does not exist or is not set.
+ ///
+ ///
+ /// For a list of common server variables available in IIS, see http://go.microsoft.com/fwlink/?LinkId=52471.
+ ///
+ public static string GetIISServerVariable(this HttpContext context, string variableName)
+ {
+ var feature = context.Features.Get();
+
+ if (feature == null)
+ {
+ return null;
+ }
+
+ return feature[variableName];
+ }
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Server.IISIntegration/IServerVariablesFeature.cs b/src/Microsoft.AspNetCore.Server.IISIntegration/IServerVariablesFeature.cs
new file mode 100644
index 0000000000..3b54733a03
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Server.IISIntegration/IServerVariablesFeature.cs
@@ -0,0 +1,24 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace Microsoft.AspNetCore.Http.Features
+{
+ ///
+ /// This feature provides access to request server variables set.
+ ///
+ /// This feature is only available when hosting ASP.NET Core in-process with IIS or IIS Express.
+ ///
+ ///
+ ///
+ /// For a list of common server variables available in IIS, see http://go.microsoft.com/fwlink/?LinkId=52471.
+ ///
+ public interface IServerVariablesFeature
+ {
+ ///
+ /// Gets the value of a server variable for the current request.
+ ///
+ /// The variable name
+ /// May return null or empty if the variable does not exist or is not set.
+ string this[string variableName] { get; }
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Server.IISIntegration/NativeMethods.cs b/src/Microsoft.AspNetCore.Server.IISIntegration/NativeMethods.cs
index ad4142100a..7a211d83d8 100644
--- a/src/Microsoft.AspNetCore.Server.IISIntegration/NativeMethods.cs
+++ b/src/Microsoft.AspNetCore.Server.IISIntegration/NativeMethods.cs
@@ -77,6 +77,9 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
[DllImport(AspNetCoreModuleDll)]
public unsafe static extern int http_get_application_properties(ref IISConfigurationData iiConfigData);
+ [DllImport(AspNetCoreModuleDll)]
+ public static extern int http_get_server_variable(IntPtr pInProcessHandler, [MarshalAs(UnmanagedType.AnsiBStr)] string variableName, [MarshalAs(UnmanagedType.BStr)] out string value);
+
[DllImport(AspNetCoreModuleDll)]
public unsafe static extern bool http_shutdown();
diff --git a/src/Microsoft.AspNetCore.Server.IISIntegration/Server/IISHttpContext.FeatureCollection.cs b/src/Microsoft.AspNetCore.Server.IISIntegration/Server/IISHttpContext.FeatureCollection.cs
index bfb182ecfc..345bd6f34e 100644
--- a/src/Microsoft.AspNetCore.Server.IISIntegration/Server/IISHttpContext.FeatureCollection.cs
+++ b/src/Microsoft.AspNetCore.Server.IISIntegration/Server/IISHttpContext.FeatureCollection.cs
@@ -25,7 +25,8 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
IHttpConnectionFeature,
IHttpRequestLifetimeFeature,
IHttpRequestIdentifierFeature,
- IHttpAuthenticationFeature
+ IHttpAuthenticationFeature,
+ IServerVariablesFeature
{
// NOTE: When feature interfaces are added to or removed from this HttpProtocol implementation,
// then the list of `implementedFeatures` in the generated code project MUST also be updated.
@@ -234,6 +235,20 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
public IAuthenticationHandler Handler { get; set; }
+ string IServerVariablesFeature.this[string variableName]
+ {
+ get
+ {
+ if (string.IsNullOrEmpty(variableName))
+ {
+ return null;
+ }
+
+ int hr = NativeMethods.http_get_server_variable(_pInProcessHandler, variableName, out var value);
+ return hr == 0 ? value : null;
+ }
+ }
+
object IFeatureCollection.this[Type key]
{
get => FastFeatureGet(key);
diff --git a/src/Microsoft.AspNetCore.Server.IISIntegration/Server/IISHttpContext.Features.cs b/src/Microsoft.AspNetCore.Server.IISIntegration/Server/IISHttpContext.Features.cs
index 5ad7590ade..6733ba74c5 100644
--- a/src/Microsoft.AspNetCore.Server.IISIntegration/Server/IISHttpContext.Features.cs
+++ b/src/Microsoft.AspNetCore.Server.IISIntegration/Server/IISHttpContext.Features.cs
@@ -3,8 +3,6 @@
using System;
using System.Collections.Generic;
-using System.Linq;
-using System.Threading.Tasks;
namespace Microsoft.AspNetCore.Server.IISIntegration
{
@@ -28,6 +26,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
private static readonly Type IHttpBodyControlFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.IHttpBodyControlFeature);
private static readonly Type IHttpSendFileFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.IHttpSendFileFeature);
private static readonly Type IISHttpContextType = typeof(IISHttpContext);
+ private static readonly Type IServerVariablesFeature = typeof(global::Microsoft.AspNetCore.Http.Features.IServerVariablesFeature);
private object _currentIHttpRequestFeature;
private object _currentIHttpResponseFeature;
@@ -49,6 +48,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
private object _currentISessionFeature;
private object _currentIHttpBodyControlFeature;
private object _currentIHttpSendFileFeature;
+ private object _currentIServerVariablesFeature;
private void Initialize()
{
@@ -63,6 +63,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
_currentIHttpMinResponseDataRateFeature = this;
_currentIHttpBodyControlFeature = this;
_currentIHttpAuthenticationFeature = this;
+ _currentIServerVariablesFeature = this;
}
internal object FastFeatureGet(Type key)
@@ -139,6 +140,10 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
{
return this;
}
+ if (key == IServerVariablesFeature)
+ {
+ return this;
+ }
return ExtraFeatureGet(key);
}
@@ -232,6 +237,11 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
_currentIHttpSendFileFeature = feature;
return;
}
+ if (key == IServerVariablesFeature)
+ {
+ _currentIServerVariablesFeature = feature;
+ return;
+ }
if (key == IISHttpContextType)
{
throw new InvalidOperationException("Cannot set IISHttpContext in feature collection");
@@ -309,6 +319,10 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
{
yield return new KeyValuePair(IHttpSendFileFeatureType, _currentIHttpSendFileFeature as global::Microsoft.AspNetCore.Http.Features.IHttpSendFileFeature);
}
+ if (_currentIServerVariablesFeature != null)
+ {
+ yield return new KeyValuePair(IServerVariablesFeature, _currentIServerVariablesFeature as global::Microsoft.AspNetCore.Http.Features.IServerVariablesFeature);
+ }
if (MaybeExtra != null)
{
diff --git a/src/RequestHandler/managedexports.cxx b/src/RequestHandler/managedexports.cxx
index a603d7a3a8..61ff3e9018 100644
--- a/src/RequestHandler/managedexports.cxx
+++ b/src/RequestHandler/managedexports.cxx
@@ -43,6 +43,37 @@ http_get_raw_response(
return pInProcessHandler->QueryHttpContext()->GetResponse()->GetRawHttpResponse();
}
+EXTERN_C __MIDL_DECLSPEC_DLLEXPORT
+HRESULT
+http_get_server_variable(
+ _In_ IN_PROCESS_HANDLER* pInProcessHandler,
+ _In_ PCSTR pszVariableName,
+ _Out_ BSTR* pwszReturn
+)
+{
+ PCWSTR pszVariableValue;
+ DWORD cbLength;
+ HRESULT hr = pInProcessHandler
+ ->QueryHttpContext()
+ ->GetServerVariable(pszVariableName, &pszVariableValue, &cbLength);
+
+ if (FAILED(hr) || cbLength == 0)
+ {
+ goto Finished;
+ }
+
+ *pwszReturn = SysAllocString(pszVariableValue);
+
+ if (*pwszReturn == NULL)
+ {
+ hr = E_OUTOFMEMORY;
+ goto Finished;
+ }
+
+Finished:
+ return hr;
+}
+
EXTERN_C __MIDL_DECLSPEC_DLLEXPORT VOID http_set_response_status_code(
_In_ IN_PROCESS_HANDLER* pInProcessHandler,
_In_ USHORT statusCode,
@@ -392,4 +423,4 @@ http_get_authentication_information(
return S_OK;
}
-// End of export
\ No newline at end of file
+// End of export
diff --git a/test/IISIntegration.IISServerFunctionalTests/AuthenticationTests.cs b/test/IISIntegration.IISServerFunctionalTests/AuthenticationTests.cs
index 2a7eaedbee..7cf1e5b60d 100644
--- a/test/IISIntegration.IISServerFunctionalTests/AuthenticationTests.cs
+++ b/test/IISIntegration.IISServerFunctionalTests/AuthenticationTests.cs
@@ -4,14 +4,13 @@
#if NET461
using System;
-using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Http;
-using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Server.IIS.FunctionalTests;
using Microsoft.AspNetCore.Server.IntegrationTesting;
+using Microsoft.AspNetCore.Testing.xunit;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Testing;
using Xunit;
@@ -26,7 +25,7 @@ namespace IISIntegration.IISServerFunctionalTests
{
}
- [Fact(Skip = "See https://github.com/aspnet/IISIntegration/issues/424")]
+ [ConditionalFact]
public Task Authentication_InProcess_IISExpress()
{
return Authentication();
diff --git a/test/IISIntegration.IISServerFunctionalTests/FeatureCollectionTests.cs b/test/IISIntegration.IISServerFunctionalTests/FeatureCollectionTests.cs
index 8c5c0ab69b..7b9a3c3a71 100644
--- a/test/IISIntegration.IISServerFunctionalTests/FeatureCollectionTests.cs
+++ b/test/IISIntegration.IISServerFunctionalTests/FeatureCollectionTests.cs
@@ -8,6 +8,7 @@ using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Server.IIS.FunctionalTests;
using Microsoft.AspNetCore.Server.IntegrationTesting;
+using Microsoft.AspNetCore.Testing.xunit;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Testing;
using Xunit;
@@ -22,7 +23,7 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests
{
}
- [Theory(Skip = "See https://github.com/aspnet/IISIntegration/issues/424")]
+ [ConditionalTheory]
[InlineData("SetRequestFeatures")]
[InlineData("SetResponseFeatures")]
[InlineData("SetConnectionFeatures")]
diff --git a/test/IISIntegration.IISServerFunctionalTests/HelloWorldTests.cs b/test/IISIntegration.IISServerFunctionalTests/HelloWorldTests.cs
index 088874867e..5682b5aaa9 100644
--- a/test/IISIntegration.IISServerFunctionalTests/HelloWorldTests.cs
+++ b/test/IISIntegration.IISServerFunctionalTests/HelloWorldTests.cs
@@ -2,11 +2,10 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
-using System.Collections.Generic;
using System.IO;
-using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Server.IntegrationTesting;
+using Microsoft.AspNetCore.Testing.xunit;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Testing;
using Xunit;
@@ -21,7 +20,7 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests
{
}
- [Fact(Skip = "See https://github.com/aspnet/IISIntegration/issues/515")]
+ [ConditionalFact]
public Task HelloWorld_InProcess_IISExpress_CoreClr_X64_Portable()
{
return HelloWorld(RuntimeFlavor.CoreClr, ApplicationType.Portable);
diff --git a/test/IISIntegration.IISServerFunctionalTests/IISIntegration.IISServerFunctionalTests.csproj b/test/IISIntegration.IISServerFunctionalTests/IISIntegration.IISServerFunctionalTests.csproj
index 1e67925136..2761d3dd76 100644
--- a/test/IISIntegration.IISServerFunctionalTests/IISIntegration.IISServerFunctionalTests.csproj
+++ b/test/IISIntegration.IISServerFunctionalTests/IISIntegration.IISServerFunctionalTests.csproj
@@ -2,6 +2,8 @@
$(StandardTestTfms)
+ Microsoft.AspNetCore.Server.IIS.FunctionalTests
+ Microsoft.AspNetCore.Server.IIS.FunctionalTests
diff --git a/test/IISIntegration.IISServerFunctionalTests/LargeResponseBodyTests.cs b/test/IISIntegration.IISServerFunctionalTests/LargeResponseBodyTests.cs
index 5db5e5ac1a..5dda2587b8 100644
--- a/test/IISIntegration.IISServerFunctionalTests/LargeResponseBodyTests.cs
+++ b/test/IISIntegration.IISServerFunctionalTests/LargeResponseBodyTests.cs
@@ -2,12 +2,10 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
-using System.Collections.Generic;
using System.IO;
-using System.Text;
using System.Threading.Tasks;
-using Microsoft.AspNetCore.Server.IIS.FunctionalTests;
using Microsoft.AspNetCore.Server.IntegrationTesting;
+using Microsoft.AspNetCore.Testing.xunit;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Testing;
using Xunit;
@@ -23,7 +21,7 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests
{
}
- [Theory(Skip = "See https://github.com/aspnet/IISIntegration/issues/424")]
+ [ConditionalTheory]
[InlineData(10000)]
[InlineData(100000)]
[InlineData(1000000)]
@@ -84,7 +82,7 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests
}
- [Fact (Skip = "See https://github.com/aspnet/IISIntegration/issues/424")]
+ [ConditionalFact]
public Task LargeFileResponseBodyInternalCheck()
{
return LargeResponseBodyFromFile(RuntimeFlavor.CoreClr, ApplicationType.Portable);
@@ -106,7 +104,12 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests
SiteName = "HttpTestSite", // This is configured in the Http.config
TargetFramework = runtimeFlavor == RuntimeFlavor.Clr ? "net461" : "netcoreapp2.0",
ApplicationType = applicationType,
-
+ Configuration =
+#if DEBUG
+ "Debug"
+#else
+ "Release"
+#endif
};
using (var deployer = ApplicationDeployerFactory.Create(deploymentParameters, loggerFactory))
diff --git a/test/IISIntegration.IISServerFunctionalTests/Properties/AssemblyInfo.cs b/test/IISIntegration.IISServerFunctionalTests/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000000..c473b24155
--- /dev/null
+++ b/test/IISIntegration.IISServerFunctionalTests/Properties/AssemblyInfo.cs
@@ -0,0 +1,5 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+// All functional tests in this project require a version of IIS express with an updated schema
+[assembly: Microsoft.AspNetCore.Server.IIS.FunctionalTests.IISExpressSupportsInProcessHosting]
diff --git a/test/IISIntegration.IISServerFunctionalTests/ResponseHeaderTests.cs b/test/IISIntegration.IISServerFunctionalTests/ResponseHeaderTests.cs
index 51fb1a5276..4dd70b8b35 100644
--- a/test/IISIntegration.IISServerFunctionalTests/ResponseHeaderTests.cs
+++ b/test/IISIntegration.IISServerFunctionalTests/ResponseHeaderTests.cs
@@ -2,13 +2,12 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
-using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
-using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Server.IntegrationTesting;
+using Microsoft.AspNetCore.Testing.xunit;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Testing;
using Microsoft.Net.Http.Headers;
@@ -24,7 +23,7 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests
{
}
- [Fact(Skip = "See https://github.com/aspnet/IISIntegration/issues/424")]
+ [ConditionalFact]
public Task AddResponseHeaders_HeaderValuesAreSetCorrectly()
{
return RunResponseHeaders(ApplicationType.Portable);
diff --git a/test/IISIntegration.IISServerFunctionalTests/ResponseInvalidOrderingTests.cs b/test/IISIntegration.IISServerFunctionalTests/ResponseInvalidOrderingTests.cs
index 3a387cc2a8..87fcb51c6f 100644
--- a/test/IISIntegration.IISServerFunctionalTests/ResponseInvalidOrderingTests.cs
+++ b/test/IISIntegration.IISServerFunctionalTests/ResponseInvalidOrderingTests.cs
@@ -5,6 +5,7 @@ using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Server.IntegrationTesting;
+using Microsoft.AspNetCore.Testing.xunit;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Testing;
using Xunit;
@@ -19,7 +20,7 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests
{
}
- [Theory(Skip = "See https://github.com/aspnet/IISIntegration/issues/424")]
+ [ConditionalTheory]
[InlineData("SetStatusCodeAfterWrite")]
[InlineData("SetHeaderAfterWrite")]
public Task ResponseInvalidOrderingTests_ExpectFailure(string path)
diff --git a/test/IISIntegration.IISServerFunctionalTests/ServerVariablesTest.cs b/test/IISIntegration.IISServerFunctionalTests/ServerVariablesTest.cs
new file mode 100644
index 0000000000..6772c57d16
--- /dev/null
+++ b/test/IISIntegration.IISServerFunctionalTests/ServerVariablesTest.cs
@@ -0,0 +1,35 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Testing.xunit;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests
+{
+ [Collection(IISTestSiteCollection.Name)]
+ public class ServerVariablesTest
+ {
+ private readonly IISTestSiteFixture _fixture;
+
+ public ServerVariablesTest(IISTestSiteFixture fixture)
+ {
+ _fixture = fixture;
+ }
+
+ [ConditionalFact]
+ public async Task ProvidesAccessToServerVariables()
+ {
+ var port = _fixture.Client.BaseAddress.Port;
+ Assert.Equal("SERVER_PORT: " + port, await _fixture.Client.GetStringAsync("/ServerVariable?q=SERVER_PORT"));
+ Assert.Equal("QUERY_STRING: q=QUERY_STRING", await _fixture.Client.GetStringAsync("/ServerVariable?q=QUERY_STRING"));
+ }
+
+ [ConditionalFact]
+ public async Task ReturnsNullForUndefinedServerVariable()
+ {
+ var port = _fixture.Client.BaseAddress.Port;
+ Assert.Equal("THIS_VAR_IS_UNDEFINED: (null)", await _fixture.Client.GetStringAsync("/ServerVariable?q=THIS_VAR_IS_UNDEFINED"));
+ }
+ }
+}
diff --git a/test/IISIntegration.IISServerFunctionalTests/Helpers.cs b/test/IISIntegration.IISServerFunctionalTests/Utilities/Helpers.cs
similarity index 100%
rename from test/IISIntegration.IISServerFunctionalTests/Helpers.cs
rename to test/IISIntegration.IISServerFunctionalTests/Utilities/Helpers.cs
diff --git a/test/IISIntegration.IISServerFunctionalTests/Utilities/IISExpressSupportsInProcessHostingAttribute.cs b/test/IISIntegration.IISServerFunctionalTests/Utilities/IISExpressSupportsInProcessHostingAttribute.cs
new file mode 100644
index 0000000000..8df2eeb406
--- /dev/null
+++ b/test/IISIntegration.IISServerFunctionalTests/Utilities/IISExpressSupportsInProcessHostingAttribute.cs
@@ -0,0 +1,62 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.IO;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Xml.Linq;
+using Microsoft.AspNetCore.Testing.xunit;
+
+namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests
+{
+ [AttributeUsage(AttributeTargets.Method | AttributeTargets.Assembly | AttributeTargets.Class)]
+ public sealed class IISExpressSupportsInProcessHostingAttribute : Attribute, ITestCondition
+ {
+ public bool IsMet => AncmSchema.SupportsInProcessHosting;
+
+ public string SkipReason => AncmSchema.SkipReason;
+
+ private class AncmSchema
+ {
+ public static bool SupportsInProcessHosting { get; }
+ public static string SkipReason { get; } = "IIS Express must be upgraded to support in-process hosting.";
+
+ static AncmSchema()
+ {
+ if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ {
+ SkipReason = "IIS Express tests can only be run on Windows";
+ return;
+ }
+
+ var ancmConfigPath = Path.Combine(
+ Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles),
+ "IIS Express", "config", "schema", "aspnetcore_schema.xml");
+
+ if (!File.Exists(ancmConfigPath))
+ {
+ SkipReason = "IIS Express is not installed.";
+ return;
+ }
+
+ XDocument ancmConfig;
+
+ try
+ {
+ ancmConfig = XDocument.Load(ancmConfigPath);
+ }
+ catch
+ {
+ SkipReason = "Could not read ANCM schema configuration";
+ return;
+ }
+
+ SupportsInProcessHosting = ancmConfig
+ .Root
+ .Descendants("attribute")
+ .Any(n => "hostingModel".Equals(n.Attribute("name")?.Value, StringComparison.Ordinal));
+ }
+ }
+ }
+}
diff --git a/test/IISIntegration.IISServerFunctionalTests/Utilities/IISTestSiteCollection.cs b/test/IISIntegration.IISServerFunctionalTests/Utilities/IISTestSiteCollection.cs
new file mode 100644
index 0000000000..88f0710e48
--- /dev/null
+++ b/test/IISIntegration.IISServerFunctionalTests/Utilities/IISTestSiteCollection.cs
@@ -0,0 +1,16 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Xunit;
+
+namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests
+{
+ ///
+ /// This type just maps collection names to available fixtures
+ ///
+ [CollectionDefinition(Name)]
+ public class IISTestSiteCollection : ICollectionFixture
+ {
+ public const string Name = nameof(IISTestSiteCollection);
+ }
+}
diff --git a/test/IISIntegration.IISServerFunctionalTests/Utilities/IISTestSiteFixture.cs b/test/IISIntegration.IISServerFunctionalTests/Utilities/IISTestSiteFixture.cs
new file mode 100644
index 0000000000..498814e4bd
--- /dev/null
+++ b/test/IISIntegration.IISServerFunctionalTests/Utilities/IISTestSiteFixture.cs
@@ -0,0 +1,52 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.IO;
+using System.Net.Http;
+using System.Threading;
+using Microsoft.AspNetCore.Server.IntegrationTesting;
+using Microsoft.Extensions.Logging.Abstractions;
+
+namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests
+{
+ public class IISTestSiteFixture : IDisposable
+ {
+ private readonly IApplicationDeployer _deployer;
+
+ public IISTestSiteFixture()
+ {
+ var deploymentParameters = new DeploymentParameters(Helpers.GetTestSitesPath(),
+ ServerType.IISExpress,
+ RuntimeFlavor.CoreClr,
+ RuntimeArchitecture.x64)
+ {
+ ServerConfigTemplateContent = File.ReadAllText("Http.config"),
+ SiteName = "HttpTestSite",
+ TargetFramework = "netcoreapp2.0",
+ ApplicationType = ApplicationType.Portable,
+ Configuration =
+#if DEBUG
+ "Debug"
+#else
+ "Release"
+#endif
+ };
+
+ _deployer = ApplicationDeployerFactory.Create(deploymentParameters, NullLoggerFactory.Instance);
+ var deploymentResult = _deployer.DeployAsync().Result;
+ Client = deploymentResult.HttpClient;
+ BaseUri = deploymentResult.ApplicationBaseUri;
+ ShutdownToken = deploymentResult.HostShutdownToken;
+ }
+
+ public string BaseUri { get; }
+ public HttpClient Client { get; }
+ public CancellationToken ShutdownToken { get; }
+
+ public void Dispose()
+ {
+ _deployer.Dispose();
+ }
+ }
+}
diff --git a/test/IISTestSite/Startup.cs b/test/IISTestSite/Startup.cs
new file mode 100644
index 0000000000..d26de2baa0
--- /dev/null
+++ b/test/IISTestSite/Startup.cs
@@ -0,0 +1,26 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Server.IIS;
+
+namespace IISTestSite
+{
+ public class Startup
+ {
+ public void Configure(IApplicationBuilder app)
+ {
+ app.Map("/ServerVariable", ServerVariable);
+ }
+
+ private void ServerVariable(IApplicationBuilder app)
+ {
+ app.Run(async ctx =>
+ {
+ var varName = ctx.Request.Query["q"];
+ await ctx.Response.WriteAsync($"{varName}: {ctx.GetIISServerVariable(varName) ?? "(null)"}");
+ });
+ }
+ }
+}
diff --git a/tools/update_schema.ps1 b/tools/update_schema.ps1
new file mode 100644
index 0000000000..a5dc6c7c03
--- /dev/null
+++ b/tools/update_schema.ps1
@@ -0,0 +1,55 @@
+<#
+.DESCRIPTION
+Updates aspnetcore_schema.xml to the latest version.
+Requires admin privileges.
+#>
+[cmdletbinding(SupportsShouldProcess = $true)]
+param()
+
+$ErrorActionPreference = 'Stop'
+Set-StrictMode -Version 1
+
+$schemaSource = Resolve-Path "$PSScriptRoot\..\src\AspNetCore\aspnetcore_schema.xml"
+[bool]$isAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")
+
+if (-not $isAdmin -and -not $WhatIfPreference) {
+ if ($PSCmdlet.ShouldContinue("Continue as an admin?", "This script needs admin privileges to update IIS Express and IIS.")) {
+ $thisFile = Join-Path $PSScriptRoot $MyInvocation.MyCommand.Name
+
+ Start-Process `
+ -Verb runas `
+ -FilePath "powershell.exe" `
+ -ArgumentList $thisFile `
+ -Wait `
+ | Out-Null
+
+ if (-not $?) {
+ throw 'Update failed'
+ }
+ exit
+ }
+ else {
+ throw 'Requires admin privileges'
+ }
+}
+
+$destinations = @(
+ "${env:ProgramFiles(x86)}\IIS Express\config\schema\aspnetcore_schema.xml",
+ "${env:ProgramFiles}\IIS Express\config\schema\aspnetcore_schema.xml",
+ "${env:windir}\system32\inetsrv\config\schema\aspnetcore_schema.xml"
+) | Get-Unique
+
+
+foreach ($dest in $destinations) {
+ if (-not (Test-Path $dest)) {
+ Write-Host -ForegroundColor Yellow "Skipping $dest. File does not already exist."
+ continue
+ }
+
+ if ($PSCmdlet.ShouldProcess($dest, "Replace file")) {
+ Write-Host "Updated $dest"
+ Move-Item $dest "${dest}.bak" -ErrorAction Ignore
+ Copy-Item $schemaSource $dest
+ }
+}
+