diff --git a/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/IISApplication.cs b/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/IISApplication.cs
deleted file mode 100644
index 81cebf1568..0000000000
--- a/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/IISApplication.cs
+++ /dev/null
@@ -1,380 +0,0 @@
-// 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.Diagnostics;
-using System.IO;
-using System.Linq;
-using System.Reflection;
-using System.Runtime.InteropServices;
-using System.Threading.Tasks;
-using System.Xml.Linq;
-using Microsoft.Extensions.Logging;
-using Microsoft.Web.Administration;
-
-namespace Microsoft.AspNetCore.Server.IntegrationTesting
-{
- ///
- /// Represents the IIS website registered in the global applicationHost.config
- ///
- internal class IISApplication
- {
- internal const int ERROR_OBJECT_NOT_FOUND = unchecked((int)0x800710D8);
- internal const int ERROR_SHARING_VIOLATION = unchecked((int)0x80070020);
-
- private static readonly TimeSpan _timeout = TimeSpan.FromSeconds(10);
- private static readonly TimeSpan _retryDelay = TimeSpan.FromMilliseconds(200);
- private readonly ServerManager _serverManager = new ServerManager();
- private readonly DeploymentParameters _deploymentParameters;
- private readonly ILogger _logger;
- private readonly string _ancmVersion;
- private readonly string _ancmDllName;
- private readonly string _apphostConfigBackupPath;
- private static readonly string _apphostConfigPath = Path.Combine(
- Environment.SystemDirectory,
- "inetsrv",
- "config",
- "applicationhost.config");
-
- public Process HostProcess { get; set; }
-
- public IISApplication(DeploymentParameters deploymentParameters, ILogger logger)
- {
- _deploymentParameters = deploymentParameters;
- _logger = logger;
- _ancmVersion = deploymentParameters.AncmVersion.ToString();
- _ancmDllName = deploymentParameters.AncmVersion == AncmVersion.AspNetCoreModuleV2 ? "aspnetcorev2.dll" : "aspnetcore.dll";
- WebSiteName = CreateTestSiteName();
- AppPoolName = $"{WebSiteName}Pool";
- _apphostConfigBackupPath = Path.Combine(
- Environment.SystemDirectory,
- "inetsrv",
- "config",
- $"applicationhost.config.{WebSiteName}backup");
- }
-
- public string WebSiteName { get; }
-
- public string AppPoolName { get; }
-
- public async Task StartIIS(Uri uri, string contentRoot)
- {
- // Backup currently deployed apphost.config file
- using (_logger.BeginScope("StartIIS"))
- {
- var port = uri.Port;
- if (port == 0)
- {
- throw new NotSupportedException("Cannot set port 0 for IIS.");
- }
- AddTemporaryAppHostConfig();
-
- ConfigureAppPool(contentRoot);
-
- ConfigureSite(contentRoot, port);
-
- ConfigureAppHostConfig();
-
- _serverManager.CommitChanges();
-
- await WaitUntilSiteStarted();
- }
- }
-
- private async Task WaitUntilSiteStarted()
- {
- var sw = Stopwatch.StartNew();
-
- while (sw.Elapsed < _timeout)
- {
- try
- {
- var serverManager = new ServerManager();
- var appPool = serverManager.ApplicationPools.FirstOrDefault(s => s.Name.Equals(AppPoolName));
- var site = serverManager.Sites.FirstOrDefault(s => s.Name.Equals(WebSiteName));
-
- if (site.State == ObjectState.Started)
- {
- var workerProcess = appPool.WorkerProcesses.SingleOrDefault();
- if (workerProcess != null)
- {
- HostProcess = Process.GetProcessById(workerProcess.ProcessId);
- _logger.LogInformation($"Site {WebSiteName} has started.");
- return;
- }
- }
- else if (site.State != ObjectState.Starting)
- {
- _logger.LogInformation($"Site hasn't started with state: {site.State.ToString()}");
- var state = site.Start();
- _logger.LogInformation($"Tried to start site, state: {state.ToString()}");
- }
- }
- catch (Exception ex) when (
- ex is DllNotFoundException ||
- ex is COMException &&
- (ex.HResult == ERROR_OBJECT_NOT_FOUND || ex.HResult == ERROR_SHARING_VIOLATION))
- {
- // Accessing the site.State property while the site
- // is starting up returns the COMException
- // The object identifier does not represent a valid object.
- // (Exception from HRESULT: 0x800710D8)
- // This also means the site is not started yet, so catch and retry
- // after waiting.
- }
-
- await Task.Delay(_retryDelay);
- }
-
- throw new TimeoutException($"IIS failed to start site.");
- }
-
- public async Task StopAndDeleteAppPool()
- {
- if (string.IsNullOrEmpty(WebSiteName))
- {
- return;
- }
-
- StopSite();
-
- StopAppPool();
-
- _serverManager.CommitChanges();
-
- await WaitUntilSiteStopped();
-
- RestoreAppHostConfig();
- }
-
- private async Task WaitUntilSiteStopped()
- {
- var site = _serverManager.Sites.Where(element => element.Name == WebSiteName).FirstOrDefault();
- if (site == null)
- {
- return;
- }
-
- var sw = Stopwatch.StartNew();
-
- while (sw.Elapsed < _timeout)
- {
- try
- {
- if (site.State == ObjectState.Stopped)
- {
- if (HostProcess.HasExited)
- {
- _logger.LogInformation($"Site {WebSiteName} has stopped successfully.");
- return;
- }
- }
- }
- catch (COMException)
- {
- // Accessing the site.State property while the site
- // is shutdown down returns the COMException
- return;
- }
-
- _logger.LogWarning($"IIS has not stopped after {sw.Elapsed.TotalMilliseconds}");
- await Task.Delay(_retryDelay);
- }
-
- throw new TimeoutException($"IIS failed to stop site {site}.");
- }
-
- private void AddTemporaryAppHostConfig()
- {
- RetryFileOperation(() => File.Move(_apphostConfigPath, _apphostConfigBackupPath),
- e => _logger.LogWarning($"Failed to backup apphost.config: {e.Message}"));
-
- _logger.LogInformation($"Backed up {_apphostConfigPath} to {_apphostConfigBackupPath}");
-
- RetryFileOperation(
- () => File.WriteAllText(_apphostConfigPath, _deploymentParameters.ServerConfigTemplateContent ?? File.ReadAllText("IIS.config")),
- e => _logger.LogWarning($"Failed to copy IIS.config to apphost.config: {e.Message}"));
-
- _logger.LogInformation($"Copied contents of IIS.config to {_apphostConfigPath}");
- }
-
- private void RestoreAppHostConfig()
- {
- if (File.Exists(_apphostConfigPath))
- {
- RetryFileOperation(
- () => File.Delete(_apphostConfigPath),
- e => _logger.LogWarning($"Failed to delete file : {e.Message}"));
- }
- if (File.Exists(_apphostConfigBackupPath))
- {
- RetryFileOperation(
- () => File.Move(_apphostConfigBackupPath, _apphostConfigPath),
- e => _logger.LogError($"Failed to backup apphost.config: {e.Message}"));
- }
- else
- {
- // Test failed to create backup config file, put a default one from IIS.config there instead.
- // An apphost.config file is required to be replaced because we use it for removing the app pool.
- RetryFileOperation(
- () => File.WriteAllText(_apphostConfigPath, File.ReadAllText("IIS.config")),
- e => _logger.LogWarning($"Failed to copy IIS.config to apphost.config: {e.Message}"));
- }
-
- _logger.LogInformation($"Restored {_apphostConfigPath}.");
- }
-
- private void RetryFileOperation(Action retryBlock, Action exceptionBlock)
- {
- RetryHelper.RetryOperation(retryBlock,
- exceptionBlock,
- retryCount: 10,
- retryDelayMilliseconds: 100);
- }
-
- private ApplicationPool ConfigureAppPool(string contentRoot)
- {
- try
- {
- var pool = _serverManager.ApplicationPools.Add(AppPoolName);
- pool.ProcessModel.IdentityType = ProcessModelIdentityType.LocalSystem;
- pool.ManagedRuntimeVersion = string.Empty;
- pool.StartMode = StartMode.AlwaysRunning;
- AddEnvironmentVariables(contentRoot, pool);
-
- _logger.LogInformation($"Configured AppPool {AppPoolName}");
- return pool;
- }
- catch (COMException comException)
- {
- _logger.LogError(File.ReadAllText(_apphostConfigPath));
- throw comException;
- }
- }
-
- private void AddEnvironmentVariables(string contentRoot, ApplicationPool pool)
- {
- try
- {
- var envCollection = pool.GetCollection("environmentVariables");
-
- foreach (var tuple in _deploymentParameters.EnvironmentVariables)
- {
- AddEnvironmentVariableToAppPool(envCollection, tuple.Key, tuple.Value);
- }
- }
- catch (COMException comException)
- {
- _logger.LogInformation($"Could not add environment variables to worker process: {comException.Message}");
- }
- }
-
- private static void AddEnvironmentVariableToAppPool(ConfigurationElementCollection envCollection, string key, string value)
- {
- var addElement = envCollection.CreateElement("add");
- addElement["name"] = key;
- addElement["value"] = value;
- envCollection.Add(addElement);
- }
-
- private Site ConfigureSite(string contentRoot, int port)
- {
- var site = _serverManager.Sites.Add(WebSiteName, contentRoot, port);
- site.Applications.Single().ApplicationPoolName = AppPoolName;
- _logger.LogInformation($"Configured Site {WebSiteName} with AppPool {AppPoolName}");
- return site;
- }
-
- private Configuration ConfigureAppHostConfig()
- {
- var config = _serverManager.GetApplicationHostConfiguration();
-
- SetGlobalModuleSection(config);
-
- SetModulesSection(config);
-
- return config;
- }
-
- private void StopSite()
- {
- var site = _serverManager.Sites.Where(sites => sites.Name == WebSiteName).SingleOrDefault();
-
- site.Stop();
- }
-
- private void StopAppPool()
- {
- var appPool = _serverManager.ApplicationPools.Where(pool => pool.Name == AppPoolName).SingleOrDefault();
-
- appPool.Stop();
- }
-
- private void SetGlobalModuleSection(Configuration config)
- {
- var ancmFile = GetAncmLocation();
-
- var globalModulesSection = config.GetSection("system.webServer/globalModules");
- var globalConfigElement = globalModulesSection
- .GetCollection()
- .Where(element => (string)element["name"] == _ancmVersion)
- .FirstOrDefault();
-
- if (globalConfigElement == null)
- {
- _logger.LogInformation($"Could not find {_ancmVersion} section in global modules; creating section.");
- var addElement = globalModulesSection.GetCollection().CreateElement("add");
- addElement["name"] = _ancmVersion;
- addElement["image"] = ancmFile;
- globalModulesSection.GetCollection().Add(addElement);
- }
- else
- {
- _logger.LogInformation($"Replacing {_ancmVersion} section in global modules with {ancmFile}");
- globalConfigElement["image"] = ancmFile;
- }
- }
-
- private void SetModulesSection(Configuration config)
- {
- var modulesSection = config.GetSection("system.webServer/modules");
- var moduleConfigElement = modulesSection.GetCollection().Where(element => (string)element["name"] == _ancmVersion).FirstOrDefault();
- if (moduleConfigElement == null)
- {
- _logger.LogInformation($"Could not find {_ancmVersion} section in modules; creating section.");
- var moduleElement = modulesSection.GetCollection().CreateElement("add");
- moduleElement["name"] = _ancmVersion;
- modulesSection.GetCollection().Add(moduleElement);
- }
- }
-
- private string CreateTestSiteName()
- {
- if (!string.IsNullOrEmpty(_deploymentParameters.SiteName))
- {
- return $"{_deploymentParameters.SiteName}{DateTime.Now.ToString("yyyyMMddHHmmss")}";
- }
- else
- {
- return $"testsite{DateTime.Now.ToString("yyyyMMddHHmmss")}";
- }
- }
-
- private string GetAncmLocation()
- {
- var dllRoot = AppContext.BaseDirectory;
- var arch = _deploymentParameters.RuntimeArchitecture == RuntimeArchitecture.x64 ? $@"x64\{_ancmDllName}" : $@"x86\{_ancmDllName}";
- var ancmFile = Path.Combine(dllRoot, arch);
- if (!File.Exists(Environment.ExpandEnvironmentVariables(ancmFile)))
- {
- ancmFile = Path.Combine(dllRoot, _ancmDllName);
- if (!File.Exists(Environment.ExpandEnvironmentVariables(ancmFile)))
- {
- throw new FileNotFoundException("AspNetCoreModule could not be found.", ancmFile);
- }
- }
-
- return ancmFile;
- }
- }
-}
diff --git a/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/IISDeployer.cs b/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/IISDeployer.cs
index acc4ae4cfb..d76f5f48d7 100644
--- a/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/IISDeployer.cs
+++ b/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/IISDeployer.cs
@@ -1,22 +1,39 @@
// 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.Diagnostics;
using System.IO;
+using System.Linq;
+using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
+using System.Xml.Linq;
using Microsoft.AspNetCore.Server.IntegrationTesting.Common;
using Microsoft.Extensions.Logging;
+using Microsoft.Web.Administration;
namespace Microsoft.AspNetCore.Server.IntegrationTesting.IIS
{
///
/// Deployer for IIS.
///
- public partial class IISDeployer : IISDeployerBase
+ public class IISDeployer : IISDeployerBase
{
- private IISApplication _application;
+ internal const int ERROR_OBJECT_NOT_FOUND = unchecked((int)0x800710D8);
+ internal const int ERROR_SHARING_VIOLATION = unchecked((int)0x80070020);
+ internal const int ERROR_SERVICE_CANNOT_ACCEPT_CTRL = unchecked((int)0x80070425);
+
+ private static readonly TimeSpan _timeout = TimeSpan.FromSeconds(10);
+ private static readonly TimeSpan _retryDelay = TimeSpan.FromMilliseconds(200);
+
private CancellationTokenSource _hostShutdownToken = new CancellationTokenSource();
+ private string _configPath;
+ private string _debugLogFile;
+
+ public Process HostProcess { get; set; }
+
public IISDeployer(DeploymentParameters deploymentParameters, ILoggerFactory loggerFactory)
: base(new IISDeploymentParameters(deploymentParameters), loggerFactory)
{
@@ -29,14 +46,11 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting.IIS
public override void Dispose()
{
- if (_application != null)
- {
- _application.StopAndDeleteAppPool().GetAwaiter().GetResult();
+ StopAndDeleteAppPool().GetAwaiter().GetResult();
- TriggerHostShutdown(_hostShutdownToken);
- }
+ TriggerHostShutdown(_hostShutdownToken);
- GetLogsFromFile($"{_application.WebSiteName}.txt");
+ GetLogsFromFile();
CleanPublishedOutput();
InvokeUserApplicationCleanup();
@@ -50,17 +64,23 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting.IIS
{
StartTimer();
- var contentRoot = string.Empty;
if (string.IsNullOrEmpty(DeploymentParameters.ServerConfigTemplateContent))
{
DeploymentParameters.ServerConfigTemplateContent = File.ReadAllText("IIS.config");
}
- _application = new IISApplication(IISDeploymentParameters, Logger);
-
// For now, only support using published output
DeploymentParameters.PublishApplicationBeforeDeployment = true;
+ // Do not override settings set on parameters
+ if (!IISDeploymentParameters.HandlerSettings.ContainsKey("debugLevel") &&
+ !IISDeploymentParameters.HandlerSettings.ContainsKey("debugFile"))
+ {
+ _debugLogFile = Path.GetTempFileName();
+ IISDeploymentParameters.HandlerSettings["debugLevel"] = "4";
+ IISDeploymentParameters.HandlerSettings["debugFile"] = _debugLogFile;
+ }
+
if (DeploymentParameters.ApplicationType == ApplicationType.Portable)
{
DefaultWebConfigActions.Add(
@@ -69,28 +89,19 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting.IIS
DotNetCommands.GetDotNetExecutable(DeploymentParameters.RuntimeArchitecture)));
}
- if (DeploymentParameters.PublishApplicationBeforeDeployment)
- {
- DotnetPublish();
- contentRoot = DeploymentParameters.PublishedApplicationRootPath;
- // Do not override settings set on parameters
- if (!IISDeploymentParameters.HandlerSettings.ContainsKey("debugLevel") &&
- !IISDeploymentParameters.HandlerSettings.ContainsKey("debugFile"))
- {
- var logFile = Path.Combine(contentRoot, $"{_application.WebSiteName}.txt");
- IISDeploymentParameters.HandlerSettings["debugLevel"] = "4";
- IISDeploymentParameters.HandlerSettings["debugFile"] = logFile;
- }
- DefaultWebConfigActions.Add(WebConfigHelpers.AddOrModifyHandlerSection(
- key: "modules",
- value: DeploymentParameters.AncmVersion.ToString()));
- RunWebConfigActions(contentRoot);
- }
+ DotnetPublish();
+ var contentRoot = DeploymentParameters.PublishedApplicationRootPath;
+
+ DefaultWebConfigActions.Add(WebConfigHelpers.AddOrModifyHandlerSection(
+ key: "modules",
+ value: DeploymentParameters.AncmVersion.ToString()));
+
+ RunWebConfigActions(contentRoot);
var uri = TestUriHelper.BuildTestUri(ServerType.IIS, DeploymentParameters.ApplicationBaseUriHint);
// To prevent modifying the IIS setup concurrently.
- await _application.StartIIS(uri, contentRoot);
+ await StartIIS(uri, contentRoot);
// Warm up time for IIS setup.
Logger.LogInformation("Successfully finished IIS application directory setup.");
@@ -100,29 +111,254 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting.IIS
applicationBaseUri: uri.ToString(),
contentRoot: contentRoot,
hostShutdownToken: _hostShutdownToken.Token,
- hostProcess: _application.HostProcess
+ hostProcess: HostProcess
);
}
}
- private void GetLogsFromFile(string file)
+ private void GetLogsFromFile()
{
var arr = new string[0];
- RetryHelper.RetryOperation(() => arr = File.ReadAllLines(Path.Combine(DeploymentParameters.PublishedApplicationRootPath, file)),
- (ex) => Logger.LogWarning("Could not read log file"),
+ RetryHelper.RetryOperation(() => arr = File.ReadAllLines(Path.Combine(DeploymentParameters.PublishedApplicationRootPath, _debugLogFile)),
+ (ex) => Logger.LogWarning(ex, "Could not read log file"),
5,
200);
- if (arr.Length == 0)
- {
- Logger.LogWarning($"{file} is empty.");
- }
-
foreach (var line in arr)
{
Logger.LogInformation(line);
}
+
+ if (File.Exists(_debugLogFile))
+ {
+ File.Delete(_debugLogFile);
+ }
+ }
+
+ public async Task StartIIS(Uri uri, string contentRoot)
+ {
+ // Backup currently deployed apphost.config file
+ using (Logger.BeginScope("StartIIS"))
+ {
+ var port = uri.Port;
+ if (port == 0)
+ {
+ throw new NotSupportedException("Cannot set port 0 for IIS.");
+ }
+
+ AddTemporaryAppHostConfig(contentRoot, port);
+
+ await WaitUntilSiteStarted();
+ }
+ }
+
+ private async Task WaitUntilSiteStarted()
+ {
+ var sw = Stopwatch.StartNew();
+
+ while (sw.Elapsed < _timeout)
+ {
+ try
+ {
+ using (var serverManager = new ServerManager())
+ {
+ var site = serverManager.Sites.Single();
+ var appPool = serverManager.ApplicationPools.Single();
+
+ if (site.State == ObjectState.Started)
+ {
+ var workerProcess = appPool.WorkerProcesses.SingleOrDefault();
+ if (workerProcess != null)
+ {
+ HostProcess = Process.GetProcessById(workerProcess.ProcessId);
+ Logger.LogInformation("Site has started.");
+ return;
+ }
+ }
+ else
+ {
+ if (appPool.State != ObjectState.Started && appPool.State != ObjectState.Starting)
+ {
+ var state = appPool.Start();
+ Logger.LogInformation($"Starting pool, state: {state.ToString()}");
+ }
+ if (site.State != ObjectState.Starting)
+ {
+ var state = site.Start();
+ Logger.LogInformation($"Starting site, state: {state.ToString()}");
+ }
+ }
+ }
+
+ }
+ catch (Exception ex) when (IsExpectedException(ex))
+ {
+ // Accessing the site.State property while the site
+ // is starting up returns the COMException
+ // The object identifier does not represent a valid object.
+ // (Exception from HRESULT: 0x800710D8)
+ // This also means the site is not started yet, so catch and retry
+ // after waiting.
+ }
+
+ await Task.Delay(_retryDelay);
+ }
+
+ throw new TimeoutException($"IIS failed to start site.");
+ }
+
+ public async Task StopAndDeleteAppPool()
+ {
+ Stop();
+
+ await WaitUntilSiteStopped();
+
+ RestoreAppHostConfig();
+ }
+
+ private async Task WaitUntilSiteStopped()
+ {
+ using (var serverManager = new ServerManager())
+ {
+ var site = serverManager.Sites.SingleOrDefault();
+ if (site == null)
+ {
+ return;
+ }
+
+ var sw = Stopwatch.StartNew();
+
+ while (sw.Elapsed < _timeout)
+ {
+ try
+ {
+ if (site.State == ObjectState.Stopped)
+ {
+ if (HostProcess.HasExited)
+ {
+ Logger.LogInformation($"Site has stopped successfully.");
+ return;
+ }
+ }
+ }
+ catch (Exception ex) when (IsExpectedException(ex))
+ {
+ // Accessing the site.State property while the site
+ // is shutdown down returns the COMException
+ return;
+ }
+
+ Logger.LogWarning($"IIS has not stopped after {sw.Elapsed.TotalMilliseconds}");
+ await Task.Delay(_retryDelay);
+ }
+
+ throw new TimeoutException($"IIS failed to stop site {site}.");
+ }
+ }
+
+ private static bool IsExpectedException(Exception ex)
+ {
+ return ex is DllNotFoundException ||
+ ex is COMException &&
+ (ex.HResult == ERROR_OBJECT_NOT_FOUND || ex.HResult == ERROR_SHARING_VIOLATION || ex.HResult == ERROR_SERVICE_CANNOT_ACCEPT_CTRL);
+ }
+
+ private void AddTemporaryAppHostConfig(string contentRoot, int port)
+ {
+ _configPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("D"));
+ var appHostConfigPath = Path.Combine(_configPath, "applicationHost.config");
+ Directory.CreateDirectory(_configPath);
+ var config = XDocument.Parse(DeploymentParameters.ServerConfigTemplateContent ?? File.ReadAllText("IIS.config"));
+
+ ConfigureAppHostConfig(config.Root, contentRoot, port);
+
+ config.Save(appHostConfigPath);
+
+ using (var serverManager = new ServerManager())
+ {
+ var redirectionConfiguration = serverManager.GetRedirectionConfiguration();
+ var redirectionSection = redirectionConfiguration.GetSection("configurationRedirection");
+
+ redirectionSection.Attributes["enabled"].Value = true;
+ redirectionSection.Attributes["path"].Value = _configPath;
+
+ serverManager.CommitChanges();
+ }
+ }
+
+ private void RestoreAppHostConfig()
+ {
+ using (var serverManager = new ServerManager())
+ {
+ var redirectionConfiguration = serverManager.GetRedirectionConfiguration();
+ var redirectionSection = redirectionConfiguration.GetSection("configurationRedirection");
+
+ redirectionSection.Attributes["enabled"].Value = false;
+
+ serverManager.CommitChanges();
+
+ Directory.Delete(_configPath, true);
+ }
+ }
+
+ private void ConfigureAppHostConfig(XElement config, string contentRoot, int port)
+ {
+ var siteElement = config
+ .RequiredElement("system.applicationHost")
+ .RequiredElement("sites")
+ .RequiredElement("site");
+
+ siteElement
+ .RequiredElement("application")
+ .RequiredElement("virtualDirectory")
+ .SetAttributeValue("physicalPath", contentRoot);
+
+ siteElement
+ .RequiredElement("bindings")
+ .RequiredElement("binding")
+ .SetAttributeValue("bindingInformation", $"*:{port}:");
+
+ var ancmVersion = DeploymentParameters.AncmVersion.ToString();
+ config
+ .RequiredElement("system.webServer")
+ .RequiredElement("globalModules")
+ .GetOrAdd("add", "name", ancmVersion)
+ .SetAttributeValue("image", GetAncmLocation(DeploymentParameters.AncmVersion));
+
+ config
+ .RequiredElement("system.webServer")
+ .RequiredElement("modules")
+ .GetOrAdd("add", "name", ancmVersion);
+
+ var pool = config
+ .RequiredElement("system.applicationHost")
+ .RequiredElement("applicationPools")
+ .RequiredElement("add");
+
+ var environmentVariables = pool
+ .GetOrAdd("environmentVariables");
+
+ foreach (var tuple in DeploymentParameters.EnvironmentVariables)
+ {
+ environmentVariables
+ .GetOrAdd("add", "name", tuple.Key)
+ .SetAttributeValue("value", tuple.Value);
+ }
+
+ RunServerConfigActions(config, contentRoot);
+ }
+
+ private void Stop()
+ {
+ using (var serverManager = new ServerManager())
+ {
+ var site = serverManager.Sites.SingleOrDefault();
+ site.Stop();
+ var appPool = serverManager.ApplicationPools.SingleOrDefault();
+ appPool.Stop();
+ serverManager.CommitChanges();
+ }
}
}
}
diff --git a/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/IISDeployerBase.cs b/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/IISDeployerBase.cs
index 8dcc3e135e..f511f9f5f1 100644
--- a/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/IISDeployerBase.cs
+++ b/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/IISDeployerBase.cs
@@ -18,6 +18,8 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting.IIS
protected List> DefaultWebConfigActions { get; } = new List>();
+ protected List> DefaultServerConfigActions { get; } = new List>();
+
public IISDeployerBase(IISDeploymentParameters deploymentParameters, ILoggerFactory loggerFactory)
: base(deploymentParameters, loggerFactory)
{
@@ -26,11 +28,6 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting.IIS
public void RunWebConfigActions(string contentRoot)
{
- if (IISDeploymentParameters == null)
- {
- return;
- }
-
if (!DeploymentParameters.PublishApplicationBeforeDeployment)
{
throw new InvalidOperationException("Cannot modify web.config file if no published output.");
@@ -38,37 +35,55 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting.IIS
var path = Path.Combine(DeploymentParameters.PublishedApplicationRootPath, "web.config");
var webconfig = XDocument.Load(path);
- var xElement = webconfig.Descendants("system.webServer").Single();
foreach (var action in DefaultWebConfigActions)
{
- action.Invoke(xElement, contentRoot);
+ action.Invoke(webconfig.Root, contentRoot);
}
- foreach (var action in IISDeploymentParameters.WebConfigActionList)
+ if (IISDeploymentParameters != null)
{
- action.Invoke(xElement, contentRoot);
+ foreach (var action in IISDeploymentParameters.WebConfigActionList)
+ {
+ action.Invoke(webconfig.Root, contentRoot);
+ }
}
webconfig.Save(path);
}
- public string RunServerConfigActions(string serverConfigString, string contentRoot)
+ public void RunServerConfigActions(XElement config, string contentRoot)
{
- if (IISDeploymentParameters == null)
+ foreach (var action in DefaultServerConfigActions)
{
- return serverConfigString;
+ action.Invoke(config, contentRoot);
}
- var serverConfig = XDocument.Parse(serverConfigString);
- var xElement = serverConfig.Descendants("configuration").FirstOrDefault();
-
- foreach (var action in IISDeploymentParameters.ServerConfigActionList)
+ if (IISDeploymentParameters != null)
{
- action.Invoke(xElement, contentRoot);
+ foreach (var action in IISDeploymentParameters.ServerConfigActionList)
+ {
+ action.Invoke(config, contentRoot);
+ }
}
- return xElement.ToString();
+ }
+
+ protected string GetAncmLocation(AncmVersion version)
+ {
+ var ancmDllName = version == AncmVersion.AspNetCoreModuleV2 ? "aspnetcorev2.dll" : "aspnetcore.dll";
+ var arch = DeploymentParameters.RuntimeArchitecture == RuntimeArchitecture.x64 ? $@"x64\{ancmDllName}" : $@"x86\{ancmDllName}";
+ var ancmFile = Path.Combine(AppContext.BaseDirectory, arch);
+ if (!File.Exists(Environment.ExpandEnvironmentVariables(ancmFile)))
+ {
+ ancmFile = Path.Combine(AppContext.BaseDirectory, ancmDllName);
+ if (!File.Exists(Environment.ExpandEnvironmentVariables(ancmFile)))
+ {
+ throw new FileNotFoundException("AspNetCoreModule could not be found.", ancmFile);
+ }
+ }
+
+ return ancmFile;
}
}
}
diff --git a/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/IISDeploymentParameters.cs b/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/IISDeploymentParameters.cs
index 8d96236295..fc8fd99efc 100644
--- a/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/IISDeploymentParameters.cs
+++ b/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/IISDeploymentParameters.cs
@@ -3,9 +3,7 @@
using System;
using System.Collections.Generic;
-using System.Linq;
using System.Xml.Linq;
-using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Server.IntegrationTesting.IIS
{
@@ -65,63 +63,47 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting.IIS
private Action AddWebConfigEnvironmentVariables()
{
- return (xElement, _) =>
+ return (element, _) =>
{
if (WebConfigBasedEnvironmentVariables.Count == 0)
{
return;
}
- var element = xElement.Descendants("environmentVariables").SingleOrDefault();
- if (element == null)
- {
- element = new XElement("environmentVariables");
- xElement.Descendants("aspNetCore").SingleOrDefault().Add(element);
- }
+ var environmentVariables = element
+ .RequiredElement("system.webServer")
+ .RequiredElement("aspNetCore")
+ .GetOrAdd("environmentVariables");
+
foreach (var envVar in WebConfigBasedEnvironmentVariables)
{
- CreateOrSetElement(element, envVar.Key, envVar.Value, "environmentVariable");
+ environmentVariables.GetOrAdd("environmentVariable", "name", envVar.Key)
+ .SetAttributeValue("value", envVar.Value);
}
};
}
private Action AddHandlerSettings()
{
- return (xElement, _) =>
+ return (element, _) =>
{
if (HandlerSettings.Count == 0)
{
return;
}
- var element = xElement.Descendants("handlerSettings").SingleOrDefault();
- if (element == null)
- {
- element = new XElement("handlerSettings");
- xElement.Descendants("aspNetCore").SingleOrDefault().Add(element);
- }
+ var handlerSettings = element
+ .RequiredElement("system.webServer")
+ .RequiredElement("aspNetCore")
+ .GetOrAdd("handlerSettings");
foreach (var handlerSetting in HandlerSettings)
{
- CreateOrSetElement(element, handlerSetting.Key, handlerSetting.Value, "handlerSetting");
+ handlerSettings.GetOrAdd("handlerSetting", "name", handlerSetting.Key)
+ .SetAttributeValue("value", handlerSetting.Value);
}
};
}
-
- private static void CreateOrSetElement(XElement rootElement, string name, string value, string elementName)
- {
- if (rootElement.Descendants()
- .Attributes()
- .Where(attribute => attribute.Value == name)
- .Any())
- {
- return;
- }
- var element = new XElement(elementName);
- element.SetAttributeValue("name", name);
- element.SetAttributeValue("value", value);
- rootElement.Add(element);
- }
}
}
diff --git a/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/IISExpressDeployer.cs b/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/IISExpressDeployer.cs
index ff02771ec0..cd1fbc69f2 100644
--- a/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/IISExpressDeployer.cs
+++ b/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/IISExpressDeployer.cs
@@ -273,8 +273,8 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting.IIS
// Pass on the applicationhost.config to iis express. With this don't need to pass in the /path /port switches as they are in the applicationHost.config
// We take a copy of the original specified applicationHost.Config to prevent modifying the one in the repo.
- serverConfig = ModifyANCMPathInConfig(replaceFlag: "[ANCMPath]", dllName: "aspnetcore.dll", serverConfig);
- serverConfig = ModifyANCMPathInConfig(replaceFlag: "[ANCMV2Path]", dllName: "aspnetcorev2.dll", serverConfig);
+ serverConfig = ModifyANCMPathInConfig(replaceFlag: "[ANCMPath]", AncmVersion.AspNetCoreModule, serverConfig);
+ serverConfig = ModifyANCMPathInConfig(replaceFlag: "[ANCMV2Path]", AncmVersion.AspNetCoreModuleV2, serverConfig);
serverConfig = ReplacePlaceholder(serverConfig, "[PORT]", port.ToString(CultureInfo.InvariantCulture));
serverConfig = ReplacePlaceholder(serverConfig, "[ApplicationPhysicalPath]", contentRoot);
@@ -299,7 +299,10 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting.IIS
serverConfig = ReplacePlaceholder(serverConfig, "[HostingModel]", DeploymentParameters.HostingModel.ToString());
serverConfig = ReplacePlaceholder(serverConfig, "[AspNetCoreModule]", DeploymentParameters.AncmVersion.ToString());
}
- serverConfig = RunServerConfigActions(serverConfig, contentRoot);
+
+ var config = XDocument.Parse(serverConfig);
+ RunServerConfigActions(config.Root, contentRoot);
+ serverConfig = config.ToString();
DeploymentParameters.ServerConfigLocation = Path.GetTempFileName();
Logger.LogDebug("Saving Config to {configPath}", DeploymentParameters.ServerConfigLocation);
@@ -317,21 +320,11 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting.IIS
return content;
}
- private string ModifyANCMPathInConfig(string replaceFlag, string dllName, string serverConfig)
+ private string ModifyANCMPathInConfig(string replaceFlag, AncmVersion version, string serverConfig)
{
- var dllRoot = AppContext.BaseDirectory;
if (serverConfig.Contains(replaceFlag))
{
- var arch = DeploymentParameters.RuntimeArchitecture == RuntimeArchitecture.x64 ? $@"x64\{dllName}" : $@"x86\{dllName}";
- var ancmFile = Path.Combine(dllRoot, arch);
- if (!File.Exists(Environment.ExpandEnvironmentVariables(ancmFile)))
- {
- ancmFile = Path.Combine(dllRoot, dllName);
- if (!File.Exists(Environment.ExpandEnvironmentVariables(ancmFile)))
- {
- throw new FileNotFoundException("AspNetCoreModule could not be found.", ancmFile);
- }
- }
+ var ancmFile = GetAncmLocation(version);
Logger.LogDebug($"Writing '{replaceFlag}' '{ancmFile}' to config");
return serverConfig.Replace(replaceFlag, ancmFile);
diff --git a/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/WebConfigHelpers.cs b/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/WebConfigHelpers.cs
index 31f103fee2..589907657f 100644
--- a/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/WebConfigHelpers.cs
+++ b/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/WebConfigHelpers.cs
@@ -2,7 +2,6 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
-using System.Linq;
using System.Xml.Linq;
namespace Microsoft.AspNetCore.Server.IntegrationTesting.IIS
@@ -10,13 +9,12 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting.IIS
public static class WebConfigHelpers
{
public static Action AddOrModifyAspNetCoreSection(string key, string value)
- => AddAction(key, value, section: "aspNetCore");
-
- public static Action AddAction(string key, string value, string section)
{
- return (element, _) =>
- {
- element.Descendants(section).SingleOrDefault().SetAttributeValue(key, value);
+ return (element, _) => {
+ element
+ .GetOrAdd("system.webServer")
+ .GetOrAdd("aspNetCore")
+ .SetAttributeValue(key, value);
};
}
@@ -24,11 +22,11 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting.IIS
{
return (element, _) =>
{
- element.Descendants("handlers")
- .FirstOrDefault()
- .Descendants("add")
- .FirstOrDefault()
- .SetAttributeValue(key, value);
+ element
+ .GetOrAdd("system.webServer")
+ .GetOrAdd("handlers")
+ .GetOrAdd("add")
+ .SetAttributeValue(key, value);
};
}
}
diff --git a/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/XElementExtensions.cs b/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/XElementExtensions.cs
new file mode 100644
index 0000000000..45ed897019
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/XElementExtensions.cs
@@ -0,0 +1,47 @@
+// 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.Linq;
+using System.Xml.Linq;
+
+namespace Microsoft.AspNetCore.Server.IntegrationTesting.IIS
+{
+ internal static class XElementExtensions
+ {
+ public static XElement RequiredElement(this XElement element, string name)
+ {
+ var existing = element.Element(name);
+ if (existing == null)
+ {
+ throw new InvalidOperationException($"Element with name {name} not found in {element}");
+ }
+
+ return existing;
+ }
+
+ public static XElement GetOrAdd(this XElement element, string name)
+ {
+ var existing = element.Element(name);
+ if (existing == null)
+ {
+ existing = new XElement(name);
+ element.Add(existing);
+ }
+
+ return existing;
+ }
+
+ public static XElement GetOrAdd(this XElement element, string name, string attribute, string attributeValue)
+ {
+ var existing = element.Elements(name).FirstOrDefault(e => e.Attribute(attribute)?.Value == attributeValue);
+ if (existing == null)
+ {
+ existing = new XElement(name, new XAttribute(attribute, attributeValue));
+ element.Add(existing);
+ }
+
+ return existing;
+ }
+ }
+}
diff --git a/test/Common.FunctionalTests/AppHostConfig/IIS.config b/test/Common.FunctionalTests/AppHostConfig/IIS.config
index 624edf91ca..bc24ef9639 100644
--- a/test/Common.FunctionalTests/AppHostConfig/IIS.config
+++ b/test/Common.FunctionalTests/AppHostConfig/IIS.config
@@ -5,7 +5,7 @@
For schema documentation, see
%windir%\system32\inetsrv\config\schema\IIS_schema.xml.
-
+
Please make a backup of this file before making any changes to it.
-->
@@ -17,20 +17,20 @@
The section controls the registration of sections.
Section is the basic unit of deployment, locking, searching and
containment for configuration settings.
-
+
Every section belongs to one section group.
A section group is a container of logically-related sections.
-
+
Sections cannot be nested.
Section groups may be nested.
-
+
-
+
The recommended way to unlock sections is by using a location tag:
@@ -131,7 +131,9 @@
-
+
+
+