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
This commit is contained in:
Nate McMaster 2018-01-27 23:57:13 -08:00
parent 6f54ed1c68
commit 011cf720e6
25 changed files with 432 additions and 29 deletions

View File

@ -6,6 +6,8 @@ branches:
- release - release
- dev - dev
- /^(.*\/)?ci-.*$/ - /^(.*\/)?ci-.*$/
install:
- ps: .\tools\update_schema.ps1
build_script: build_script:
- ps: .\run.ps1 default-build - ps: .\run.ps1 default-build
clone_depth: 1 clone_depth: 1
@ -15,4 +17,4 @@ environment:
DOTNET_CLI_TELEMETRY_OPTOUT: 1 DOTNET_CLI_TELEMETRY_OPTOUT: 1
test: off test: off
deploy: off deploy: off
os: Visual Studio 2017 os: Visual Studio 2017

View File

@ -1,7 +1,7 @@
 
Microsoft Visual Studio Solution File, Format Version 12.00 Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15 # Visual Studio 15
VisualStudioVersion = 15.0.27130.2020 VisualStudioVersion = 15.0.27130.2026
MinimumVisualStudioVersion = 15.0.26730.03 MinimumVisualStudioVersion = 15.0.26730.03
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{04B1EDB6-E967-4D25-89B9-E6F8304038CD}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{04B1EDB6-E967-4D25-89B9-E6F8304038CD}"
ProjectSection(SolutionItems) = preProject ProjectSection(SolutionItems) = preProject

View File

@ -9,13 +9,13 @@
<ItemGroup Condition="'$(OS)' == 'Windows_NT'"> <ItemGroup Condition="'$(OS)' == 'Windows_NT'">
<!-- x86 --> <!-- x86 -->
<None Include="..\..\src\RequestHandler\bin\$(Configuration)\Win32\aspnetcorerh.dll" CopyToOutputDirectory="PreserveNewest" Visible="false" Link="x86\aspnetcorerh.dll" /> <None Include="..\..\src\RequestHandler\bin\$(Configuration)\Win32\*.*" CopyToOutputDirectory="PreserveNewest" Visible="false" Link="x86\%(FileName)%(Extension)" />
<None Include="..\..\src\AspNetCore\bin\$(Configuration)\Win32\aspnetcore.dll" CopyToOutputDirectory="PreserveNewest" Visible="false" Link="x86\aspnetcore.dll" /> <None Include="..\..\src\AspNetCore\bin\$(Configuration)\Win32\*.*" CopyToOutputDirectory="PreserveNewest" Visible="false" Link="x86\%(FileName)%(Extension)" />
<!-- x64 --> <!-- x64 -->
<None Include="..\..\src\RequestHandler\bin\$(Configuration)\x64\aspnetcorerh.dll" CopyToOutputDirectory="PreserveNewest" Visible="false" Link="x64\aspnetcorerh.dll" /> <None Include="..\..\src\RequestHandler\bin\$(Configuration)\x64\*.*" CopyToOutputDirectory="PreserveNewest" Visible="false" Link="x64\%(FileName)%(Extension)" />
<None Include="..\..\src\AspNetCore\bin\$(Configuration)\x64\aspnetcore.dll" CopyToOutputDirectory="PreserveNewest" Visible="false" Link="x64\aspnetcore.dll" /> <None Include="..\..\src\AspNetCore\bin\$(Configuration)\x64\*.*" CopyToOutputDirectory="PreserveNewest" Visible="false" Link="x64\%(FileName)%(Extension)" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Hosting" Version="$(MicrosoftAspNetCoreHostingPackageVersion)" /> <PackageReference Include="Microsoft.AspNetCore.Hosting" Version="$(MicrosoftAspNetCoreHostingPackageVersion)" />
</ItemGroup> </ItemGroup>

View File

@ -7,19 +7,20 @@ using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Server.IIS;
using Microsoft.AspNetCore.Server.IISIntegration; using Microsoft.AspNetCore.Server.IISIntegration;
namespace NativeIISSample namespace NativeIISSample
{ {
public class Startup public class Startup
{ {
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. // 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) public void Configure(IApplicationBuilder app, IHostingEnvironment env, IAuthenticationSchemeProvider authSchemeProvider)
{ {
app.Run(async (context) => app.Run(async (context) =>
{ {
context.Response.ContentType = "text/plain"; context.Response.ContentType = "text/plain";
await context.Response.WriteAsync("Hello World - " + DateTimeOffset.Now + Environment.NewLine); await context.Response.WriteAsync("Hello World - " + DateTimeOffset.Now + Environment.NewLine);
await context.Response.WriteAsync(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(key + ": " + value + Environment.NewLine);
} }
await context.Response.WriteAsync(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) public static void Main(string[] args)
{ {
var host = new WebHostBuilder() var host = new WebHostBuilder()

View File

@ -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
{
/// <summary>
/// Extensions to <see cref="HttpContext"/> that enable access to IIS features.
/// </summary>
public static class IISHttpContextExtensions
{
/// <summary>
/// Gets the value of a server variable for the current request.
/// </summary>
/// <param name="context">The http context for the request.</param>
/// <param name="variableName">The name of the variable.</param>
/// <returns>
/// <c>null</c> if the feature does not support the <see cref="IServerVariablesFeature"/> feature.
/// May return null or empty if the variable does not exist or is not set.
/// </returns>
/// <remarks>
/// For a list of common server variables available in IIS, see http://go.microsoft.com/fwlink/?LinkId=52471.
/// </remarks>
public static string GetIISServerVariable(this HttpContext context, string variableName)
{
var feature = context.Features.Get<IServerVariablesFeature>();
if (feature == null)
{
return null;
}
return feature[variableName];
}
}
}

View File

@ -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
{
/// <summary>
/// This feature provides access to request server variables set.
/// <para>
/// This feature is only available when hosting ASP.NET Core in-process with IIS or IIS Express.
/// </para>
/// </summary>
/// <remarks>
/// For a list of common server variables available in IIS, see http://go.microsoft.com/fwlink/?LinkId=52471.
/// </remarks>
public interface IServerVariablesFeature
{
/// <summary>
/// Gets the value of a server variable for the current request.
/// </summary>
/// <param name="variableName">The variable name</param>
/// <returns>May return null or empty if the variable does not exist or is not set.</returns>
string this[string variableName] { get; }
}
}

View File

@ -77,6 +77,9 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
[DllImport(AspNetCoreModuleDll)] [DllImport(AspNetCoreModuleDll)]
public unsafe static extern int http_get_application_properties(ref IISConfigurationData iiConfigData); 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)] [DllImport(AspNetCoreModuleDll)]
public unsafe static extern bool http_shutdown(); public unsafe static extern bool http_shutdown();

View File

@ -25,7 +25,8 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
IHttpConnectionFeature, IHttpConnectionFeature,
IHttpRequestLifetimeFeature, IHttpRequestLifetimeFeature,
IHttpRequestIdentifierFeature, IHttpRequestIdentifierFeature,
IHttpAuthenticationFeature IHttpAuthenticationFeature,
IServerVariablesFeature
{ {
// NOTE: When feature interfaces are added to or removed from this HttpProtocol implementation, // 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. // 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; } 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] object IFeatureCollection.this[Type key]
{ {
get => FastFeatureGet(key); get => FastFeatureGet(key);

View File

@ -3,8 +3,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Microsoft.AspNetCore.Server.IISIntegration 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 IHttpBodyControlFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.IHttpBodyControlFeature);
private static readonly Type IHttpSendFileFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.IHttpSendFileFeature); private static readonly Type IHttpSendFileFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.IHttpSendFileFeature);
private static readonly Type IISHttpContextType = typeof(IISHttpContext); private static readonly Type IISHttpContextType = typeof(IISHttpContext);
private static readonly Type IServerVariablesFeature = typeof(global::Microsoft.AspNetCore.Http.Features.IServerVariablesFeature);
private object _currentIHttpRequestFeature; private object _currentIHttpRequestFeature;
private object _currentIHttpResponseFeature; private object _currentIHttpResponseFeature;
@ -49,6 +48,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
private object _currentISessionFeature; private object _currentISessionFeature;
private object _currentIHttpBodyControlFeature; private object _currentIHttpBodyControlFeature;
private object _currentIHttpSendFileFeature; private object _currentIHttpSendFileFeature;
private object _currentIServerVariablesFeature;
private void Initialize() private void Initialize()
{ {
@ -63,6 +63,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
_currentIHttpMinResponseDataRateFeature = this; _currentIHttpMinResponseDataRateFeature = this;
_currentIHttpBodyControlFeature = this; _currentIHttpBodyControlFeature = this;
_currentIHttpAuthenticationFeature = this; _currentIHttpAuthenticationFeature = this;
_currentIServerVariablesFeature = this;
} }
internal object FastFeatureGet(Type key) internal object FastFeatureGet(Type key)
@ -139,6 +140,10 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
{ {
return this; return this;
} }
if (key == IServerVariablesFeature)
{
return this;
}
return ExtraFeatureGet(key); return ExtraFeatureGet(key);
} }
@ -232,6 +237,11 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
_currentIHttpSendFileFeature = feature; _currentIHttpSendFileFeature = feature;
return; return;
} }
if (key == IServerVariablesFeature)
{
_currentIServerVariablesFeature = feature;
return;
}
if (key == IISHttpContextType) if (key == IISHttpContextType)
{ {
throw new InvalidOperationException("Cannot set IISHttpContext in feature collection"); throw new InvalidOperationException("Cannot set IISHttpContext in feature collection");
@ -309,6 +319,10 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
{ {
yield return new KeyValuePair<Type, object>(IHttpSendFileFeatureType, _currentIHttpSendFileFeature as global::Microsoft.AspNetCore.Http.Features.IHttpSendFileFeature); yield return new KeyValuePair<Type, object>(IHttpSendFileFeatureType, _currentIHttpSendFileFeature as global::Microsoft.AspNetCore.Http.Features.IHttpSendFileFeature);
} }
if (_currentIServerVariablesFeature != null)
{
yield return new KeyValuePair<Type, object>(IServerVariablesFeature, _currentIServerVariablesFeature as global::Microsoft.AspNetCore.Http.Features.IServerVariablesFeature);
}
if (MaybeExtra != null) if (MaybeExtra != null)
{ {

View File

@ -43,6 +43,37 @@ http_get_raw_response(
return pInProcessHandler->QueryHttpContext()->GetResponse()->GetRawHttpResponse(); 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( EXTERN_C __MIDL_DECLSPEC_DLLEXPORT VOID http_set_response_status_code(
_In_ IN_PROCESS_HANDLER* pInProcessHandler, _In_ IN_PROCESS_HANDLER* pInProcessHandler,
_In_ USHORT statusCode, _In_ USHORT statusCode,
@ -392,4 +423,4 @@ http_get_authentication_information(
return S_OK; return S_OK;
} }
// End of export // End of export

View File

@ -4,14 +4,13 @@
#if NET461 #if NET461
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Net; using System.Net;
using System.Net.Http; using System.Net.Http;
using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Server.IIS.FunctionalTests; using Microsoft.AspNetCore.Server.IIS.FunctionalTests;
using Microsoft.AspNetCore.Server.IntegrationTesting; using Microsoft.AspNetCore.Server.IntegrationTesting;
using Microsoft.AspNetCore.Testing.xunit;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Testing; using Microsoft.Extensions.Logging.Testing;
using Xunit; 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() public Task Authentication_InProcess_IISExpress()
{ {
return Authentication(); return Authentication();

View File

@ -8,6 +8,7 @@ using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Server.IIS.FunctionalTests; using Microsoft.AspNetCore.Server.IIS.FunctionalTests;
using Microsoft.AspNetCore.Server.IntegrationTesting; using Microsoft.AspNetCore.Server.IntegrationTesting;
using Microsoft.AspNetCore.Testing.xunit;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Testing; using Microsoft.Extensions.Logging.Testing;
using Xunit; 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("SetRequestFeatures")]
[InlineData("SetResponseFeatures")] [InlineData("SetResponseFeatures")]
[InlineData("SetConnectionFeatures")] [InlineData("SetConnectionFeatures")]

View File

@ -2,11 +2,10 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Server.IntegrationTesting; using Microsoft.AspNetCore.Server.IntegrationTesting;
using Microsoft.AspNetCore.Testing.xunit;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Testing; using Microsoft.Extensions.Logging.Testing;
using Xunit; 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() public Task HelloWorld_InProcess_IISExpress_CoreClr_X64_Portable()
{ {
return HelloWorld(RuntimeFlavor.CoreClr, ApplicationType.Portable); return HelloWorld(RuntimeFlavor.CoreClr, ApplicationType.Portable);

View File

@ -2,6 +2,8 @@
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>$(StandardTestTfms)</TargetFrameworks> <TargetFrameworks>$(StandardTestTfms)</TargetFrameworks>
<RootNamespace>Microsoft.AspNetCore.Server.IIS.FunctionalTests</RootNamespace>
<AssemblyName>Microsoft.AspNetCore.Server.IIS.FunctionalTests</AssemblyName>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>

View File

@ -2,12 +2,10 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Server.IIS.FunctionalTests;
using Microsoft.AspNetCore.Server.IntegrationTesting; using Microsoft.AspNetCore.Server.IntegrationTesting;
using Microsoft.AspNetCore.Testing.xunit;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Testing; using Microsoft.Extensions.Logging.Testing;
using Xunit; 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(10000)]
[InlineData(100000)] [InlineData(100000)]
[InlineData(1000000)] [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() public Task LargeFileResponseBodyInternalCheck()
{ {
return LargeResponseBodyFromFile(RuntimeFlavor.CoreClr, ApplicationType.Portable); 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 SiteName = "HttpTestSite", // This is configured in the Http.config
TargetFramework = runtimeFlavor == RuntimeFlavor.Clr ? "net461" : "netcoreapp2.0", TargetFramework = runtimeFlavor == RuntimeFlavor.Clr ? "net461" : "netcoreapp2.0",
ApplicationType = applicationType, ApplicationType = applicationType,
Configuration =
#if DEBUG
"Debug"
#else
"Release"
#endif
}; };
using (var deployer = ApplicationDeployerFactory.Create(deploymentParameters, loggerFactory)) using (var deployer = ApplicationDeployerFactory.Create(deploymentParameters, loggerFactory))

View File

@ -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]

View File

@ -2,13 +2,12 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Server.IntegrationTesting; using Microsoft.AspNetCore.Server.IntegrationTesting;
using Microsoft.AspNetCore.Testing.xunit;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Testing; using Microsoft.Extensions.Logging.Testing;
using Microsoft.Net.Http.Headers; 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() public Task AddResponseHeaders_HeaderValuesAreSetCorrectly()
{ {
return RunResponseHeaders(ApplicationType.Portable); return RunResponseHeaders(ApplicationType.Portable);

View File

@ -5,6 +5,7 @@ using System;
using System.IO; using System.IO;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Server.IntegrationTesting; using Microsoft.AspNetCore.Server.IntegrationTesting;
using Microsoft.AspNetCore.Testing.xunit;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Testing; using Microsoft.Extensions.Logging.Testing;
using Xunit; 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("SetStatusCodeAfterWrite")]
[InlineData("SetHeaderAfterWrite")] [InlineData("SetHeaderAfterWrite")]
public Task ResponseInvalidOrderingTests_ExpectFailure(string path) public Task ResponseInvalidOrderingTests_ExpectFailure(string path)

View File

@ -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"));
}
}
}

View File

@ -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));
}
}
}
}

View File

@ -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
{
/// <summary>
/// This type just maps collection names to available fixtures
/// </summary>
[CollectionDefinition(Name)]
public class IISTestSiteCollection : ICollectionFixture<IISTestSiteFixture>
{
public const string Name = nameof(IISTestSiteCollection);
}
}

View File

@ -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();
}
}
}

View File

@ -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)"}");
});
}
}
}

55
tools/update_schema.ps1 Normal file
View File

@ -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
}
}