From 011cf720e6fcb049946991339d5213a70167c8b4 Mon Sep 17 00:00:00 2001 From: Nate McMaster Date: Sat, 27 Jan 2018 23:57:13 -0800 Subject: [PATCH] Add IServerVariablesFeature Enables fetching variables directly from IIS when using in-process hosting. This is not available for out-of-process hosting. Other changes: - Update tests to only run if IIS Express has been updated to support the new schema for hostingModel - Add a simpler test fixture for in-proc testing --- .appveyor.yml | 4 +- IISIntegration.sln | 2 +- .../NativeIISSample/NativeIISSample.csproj | 10 +-- samples/NativeIISSample/Startup.cs | 23 ++++++- .../IISHttpContextExtensions.cs | 38 ++++++++++++ .../IServerVariablesFeature.cs | 24 +++++++ .../NativeMethods.cs | 3 + .../IISHttpContext.FeatureCollection.cs | 17 ++++- .../Server/IISHttpContext.Features.cs | 18 +++++- src/RequestHandler/managedexports.cxx | 33 +++++++++- .../AuthenticationTests.cs | 5 +- .../FeatureCollectionTests.cs | 3 +- .../HelloWorldTests.cs | 5 +- ...ntegration.IISServerFunctionalTests.csproj | 2 + .../LargeResponseBodyTests.cs | 15 +++-- .../Properties/AssemblyInfo.cs | 5 ++ .../ResponseHeaderTests.cs | 5 +- .../ResponseInvalidOrderingTests.cs | 3 +- .../ServerVariablesTest.cs | 35 +++++++++++ .../{ => Utilities}/Helpers.cs | 0 ...xpressSupportsInProcessHostingAttribute.cs | 62 +++++++++++++++++++ .../Utilities/IISTestSiteCollection.cs | 16 +++++ .../Utilities/IISTestSiteFixture.cs | 52 ++++++++++++++++ test/IISTestSite/Startup.cs | 26 ++++++++ tools/update_schema.ps1 | 55 ++++++++++++++++ 25 files changed, 432 insertions(+), 29 deletions(-) create mode 100644 src/Microsoft.AspNetCore.Server.IISIntegration/IISHttpContextExtensions.cs create mode 100644 src/Microsoft.AspNetCore.Server.IISIntegration/IServerVariablesFeature.cs create mode 100644 test/IISIntegration.IISServerFunctionalTests/Properties/AssemblyInfo.cs create mode 100644 test/IISIntegration.IISServerFunctionalTests/ServerVariablesTest.cs rename test/IISIntegration.IISServerFunctionalTests/{ => Utilities}/Helpers.cs (100%) create mode 100644 test/IISIntegration.IISServerFunctionalTests/Utilities/IISExpressSupportsInProcessHostingAttribute.cs create mode 100644 test/IISIntegration.IISServerFunctionalTests/Utilities/IISTestSiteCollection.cs create mode 100644 test/IISIntegration.IISServerFunctionalTests/Utilities/IISTestSiteFixture.cs create mode 100644 test/IISTestSite/Startup.cs create mode 100644 tools/update_schema.ps1 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 + } +} +