From 68dc961abbf6f24e75dfe9a16b8541f671967326 Mon Sep 17 00:00:00 2001 From: "ASP.NET CI" Date: Sun, 8 Jul 2018 12:13:44 -0700 Subject: [PATCH 1/6] Update dependencies.props [auto-updated: dependencies] --- build/dependencies.props | 64 ++++++++++++++++++++-------------------- korebuild-lock.txt | 4 +-- 2 files changed, 34 insertions(+), 34 deletions(-) diff --git a/build/dependencies.props b/build/dependencies.props index b78e93ab..2f4eb5da 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -3,37 +3,37 @@ $(MSBuildAllProjects);$(MSBuildThisFileFullPath) - 2.2.0-preview1-17090 - 2.2.0-preview1-34530 - 2.2.0-preview1-34530 - 2.2.0-preview1-34530 - 2.2.0-preview1-34530 - 2.2.0-preview1-34530 - 2.2.0-preview1-34530 - 2.2.0-preview1-34530 - 2.2.0-preview1-34530 - 2.2.0-preview1-34530 - 2.2.0-preview1-34530 - 2.2.0-preview1-34530 - 2.2.0-preview1-34530 - 2.2.0-preview1-34530 - 2.2.0-preview1-34530 - 2.2.0-preview1-34530 - 2.2.0-preview1-34530 - 2.2.0-preview1-34530 - 2.2.0-preview1-34530 - 2.2.0-preview1-34530 - 2.2.0-preview1-34530 - 2.2.0-preview1-34530 - 2.2.0-preview1-34530 - 2.2.0-preview1-34530 - 2.2.0-preview1-34530 - 2.2.0-preview1-34530 - 2.2.0-preview1-34530 - 2.2.0-preview1-34530 - 2.2.0-preview1-34530 - 2.0.0 - 2.1.0 + 2.2.0-preview1-17099 + 2.2.0-preview1-34640 + 2.2.0-preview1-34640 + 2.2.0-preview1-34640 + 2.2.0-preview1-34640 + 2.2.0-preview1-34640 + 2.2.0-preview1-34640 + 2.2.0-preview1-34640 + 2.2.0-preview1-34640 + 2.2.0-preview1-34640 + 2.2.0-preview1-34640 + 2.2.0-preview1-34640 + 2.2.0-preview1-34640 + 2.2.0-preview1-34640 + 2.2.0-preview1-34640 + 2.2.0-preview1-34640 + 2.2.0-preview1-34640 + 2.2.0-preview1-34640 + 2.2.0-preview1-34640 + 2.2.0-preview1-34640 + 2.2.0-preview1-34640 + 2.2.0-preview1-34640 + 2.2.0-preview1-34640 + 2.2.0-preview1-34640 + 2.2.0-preview1-34640 + 2.2.0-preview1-34640 + 2.2.0-preview1-34640 + 2.2.0-preview1-34640 + 2.2.0-preview1-34640 + 2.0.7 + 2.1.1 2.2.0-preview1-26618-02 1.0.1 15.6.1 @@ -46,7 +46,7 @@ 1.7.0-preview1-26617-01 4.6.0-preview1-26617-01 2.3.1 - 2.4.0-beta.1.build3945 + 2.4.0-rc.1.build4038 diff --git a/korebuild-lock.txt b/korebuild-lock.txt index 3e694b2e..8b9d1782 100644 --- a/korebuild-lock.txt +++ b/korebuild-lock.txt @@ -1,2 +1,2 @@ -version:2.2.0-preview1-17090 -commithash:b19e903e946579cd9482089bce7d917e8bacd765 +version:2.2.0-preview1-17099 +commithash:263ed1db9866b6b419b1f5d5189a712aa218acb3 From 51c94337d7755dc4970da77f2ee12285da176d86 Mon Sep 17 00:00:00 2001 From: Pavel Krymets Date: Tue, 10 Jul 2018 11:29:39 -0700 Subject: [PATCH 2/6] /systray:false (#1486) --- .../Deployers/IISExpressDeployer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.AspNetCore.Server.IntegrationTesting/Deployers/IISExpressDeployer.cs b/src/Microsoft.AspNetCore.Server.IntegrationTesting/Deployers/IISExpressDeployer.cs index 1bdc58ec..8a2cc6cf 100644 --- a/src/Microsoft.AspNetCore.Server.IntegrationTesting/Deployers/IISExpressDeployer.cs +++ b/src/Microsoft.AspNetCore.Server.IntegrationTesting/Deployers/IISExpressDeployer.cs @@ -151,8 +151,8 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting PrepareConfig(contentRoot, dllRoot, port); var parameters = string.IsNullOrEmpty(DeploymentParameters.ServerConfigLocation) ? - string.Format("/port:{0} /path:\"{1}\" /trace:error", uri.Port, contentRoot) : - string.Format("/site:{0} /config:{1} /trace:error", DeploymentParameters.SiteName, DeploymentParameters.ServerConfigLocation); + string.Format("/port:{0} /path:\"{1}\" /trace:error /systray:false", uri.Port, contentRoot) : + string.Format("/site:{0} /config:{1} /trace:error /systray:false", DeploymentParameters.SiteName, DeploymentParameters.ServerConfigLocation); var iisExpressPath = GetIISExpressPath(); From 9149f5e4cfe309711ec61b5c6cc5dd5940d5b287 Mon Sep 17 00:00:00 2001 From: "ASP.NET CI" Date: Sun, 15 Jul 2018 12:13:31 -0700 Subject: [PATCH 3/6] Update dependencies.props [auto-updated: dependencies] --- build/dependencies.props | 68 ++++++++++++++++++++-------------------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/build/dependencies.props b/build/dependencies.props index 2f4eb5da..e4f62095 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -4,36 +4,36 @@ 2.2.0-preview1-17099 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.0.7 - 2.1.1 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.0.9 + 2.1.2 2.2.0-preview1-26618-02 1.0.1 15.6.1 @@ -41,10 +41,10 @@ 2.0.3 1.4.0 3.2.0 - 4.6.0-preview1-26617-01 - 4.6.0-preview1-26617-01 - 1.7.0-preview1-26617-01 - 4.6.0-preview1-26617-01 + 4.5.0 + 4.5.0 + 1.6.0 + 4.5.0 2.3.1 2.4.0-rc.1.build4038 From ec0b7ab2cd6c351199ba7956fbc3a93d9f70923f Mon Sep 17 00:00:00 2001 From: Pavel Krymets Date: Tue, 17 Jul 2018 10:16:50 -0700 Subject: [PATCH 4/6] Remove IISExpress deployer (#1493) --- .../Common/DotNetCommands.cs | 8 +- .../Common/TestUriHelper.cs | 9 +- .../Deployers/ApplicationDeployerFactory.cs | 3 +- .../Deployers/IISExpressDeployer.cs | 436 ------------------ 4 files changed, 8 insertions(+), 448 deletions(-) delete mode 100644 src/Microsoft.AspNetCore.Server.IntegrationTesting/Deployers/IISExpressDeployer.cs diff --git a/src/Microsoft.AspNetCore.Server.IntegrationTesting/Common/DotNetCommands.cs b/src/Microsoft.AspNetCore.Server.IntegrationTesting/Common/DotNetCommands.cs index d9f55770..51414544 100644 --- a/src/Microsoft.AspNetCore.Server.IntegrationTesting/Common/DotNetCommands.cs +++ b/src/Microsoft.AspNetCore.Server.IntegrationTesting/Common/DotNetCommands.cs @@ -7,14 +7,14 @@ using System.Runtime.InteropServices; namespace Microsoft.AspNetCore.Server.IntegrationTesting { - internal static class DotNetCommands + public static class DotNetCommands { private const string _dotnetFolderName = ".dotnet"; internal static string DotNetHome { get; } = GetDotNetHome(); // Compare to https://github.com/aspnet/BuildTools/blob/314c98e4533217a841ff9767bb38e144eb6c93e4/tools/KoreBuild.Console/Commands/CommandContext.cs#L76 - private static string GetDotNetHome() + public static string GetDotNetHome() { var dotnetHome = Environment.GetEnvironmentVariable("DOTNET_HOME"); var userProfile = Environment.GetEnvironmentVariable("USERPROFILE"); @@ -37,7 +37,7 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting return result; } - internal static string GetDotNetInstallDir(RuntimeArchitecture arch) + public static string GetDotNetInstallDir(RuntimeArchitecture arch) { var dotnetDir = DotNetHome; if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) @@ -48,7 +48,7 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting return dotnetDir; } - internal static string GetDotNetExecutable(RuntimeArchitecture arch) + public static string GetDotNetExecutable(RuntimeArchitecture arch) { var dotnetDir = GetDotNetInstallDir(arch); diff --git a/src/Microsoft.AspNetCore.Server.IntegrationTesting/Common/TestUriHelper.cs b/src/Microsoft.AspNetCore.Server.IntegrationTesting/Common/TestUriHelper.cs index 1c09b08d..5a79a31c 100644 --- a/src/Microsoft.AspNetCore.Server.IntegrationTesting/Common/TestUriHelper.cs +++ b/src/Microsoft.AspNetCore.Server.IntegrationTesting/Common/TestUriHelper.cs @@ -2,19 +2,16 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Net; -using System.Net.Sockets; - namespace Microsoft.AspNetCore.Server.IntegrationTesting.Common { - internal static class TestUriHelper + public static class TestUriHelper { - internal static Uri BuildTestUri(ServerType serverType) + public static Uri BuildTestUri(ServerType serverType) { return BuildTestUri(serverType, hint: null); } - internal static Uri BuildTestUri(ServerType serverType, string hint) + public static Uri BuildTestUri(ServerType serverType, string hint) { // Assume status messages are enabled for Kestrel and disabled for all other servers. var statusMessagesEnabled = (serverType == ServerType.Kestrel); diff --git a/src/Microsoft.AspNetCore.Server.IntegrationTesting/Deployers/ApplicationDeployerFactory.cs b/src/Microsoft.AspNetCore.Server.IntegrationTesting/Deployers/ApplicationDeployerFactory.cs index 76f3588d..959f4ffe 100644 --- a/src/Microsoft.AspNetCore.Server.IntegrationTesting/Deployers/ApplicationDeployerFactory.cs +++ b/src/Microsoft.AspNetCore.Server.IntegrationTesting/Deployers/ApplicationDeployerFactory.cs @@ -32,9 +32,8 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting switch (deploymentParameters.ServerType) { case ServerType.IISExpress: - return new IISExpressDeployer(deploymentParameters, loggerFactory); case ServerType.IIS: - throw new NotSupportedException("The IIS deployer is no longer supported"); + throw new NotSupportedException("Use Microsoft.AspNetCore.Server.IntegrationTesting.IIS package and IISApplicationDeployerFactory for IIS support."); case ServerType.HttpSys: case ServerType.Kestrel: return new SelfHostDeployer(deploymentParameters, loggerFactory); diff --git a/src/Microsoft.AspNetCore.Server.IntegrationTesting/Deployers/IISExpressDeployer.cs b/src/Microsoft.AspNetCore.Server.IntegrationTesting/Deployers/IISExpressDeployer.cs deleted file mode 100644 index 8a2cc6cf..00000000 --- a/src/Microsoft.AspNetCore.Server.IntegrationTesting/Deployers/IISExpressDeployer.cs +++ /dev/null @@ -1,436 +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.Globalization; -using System.IO; -using System.Linq; -using System.Text.RegularExpressions; -using System.Threading; -using System.Threading.Tasks; -using System.Xml.Linq; -using Microsoft.AspNetCore.Server.IntegrationTesting.Common; -using Microsoft.AspNetCore.Testing; -using Microsoft.Extensions.Logging; - -namespace Microsoft.AspNetCore.Server.IntegrationTesting -{ - /// - /// Deployment helper for IISExpress. - /// - public class IISExpressDeployer : ApplicationDeployer - { - private const string IISExpressRunningMessage = "IIS Express is running."; - private const string FailedToInitializeBindingsMessage = "Failed to initialize site bindings"; - private const string UnableToStartIISExpressMessage = "Unable to start iisexpress."; - private const int MaximumAttempts = 5; - - private static readonly Regex UrlDetectorRegex = new Regex(@"^\s*Successfully registered URL ""(?[^""]+)"" for site.*$"); - - private Process _hostProcess; - - public IISExpressDeployer(DeploymentParameters deploymentParameters, ILoggerFactory loggerFactory) - : base(deploymentParameters, loggerFactory) - { - } - - public override async Task DeployAsync() - { - using (Logger.BeginScope("Deployment")) - { - // Start timer - StartTimer(); - - // For an unpublished application the dllroot points pre-built dlls like projectdir/bin/debug/net461/ - // and contentRoot points to the project directory so you get things like static assets. - // For a published app both point to the publish directory. - var dllRoot = CheckIfPublishIsRequired(); - var contentRoot = string.Empty; - if (DeploymentParameters.PublishApplicationBeforeDeployment) - { - DotnetPublish(); - contentRoot = DeploymentParameters.PublishedApplicationRootPath; - dllRoot = contentRoot; - } - else - { - // Core+Standalone always publishes. This must be Clr+Standalone or Core+Portable. - // Update processPath and arguments for our current scenario - contentRoot = DeploymentParameters.ApplicationPath; - - var executableExtension = DeploymentParameters.ApplicationType == ApplicationType.Portable ? ".dll" : ".exe"; - var entryPoint = Path.Combine(dllRoot, DeploymentParameters.ApplicationName + executableExtension); - - var executableName = string.Empty; - var executableArgs = string.Empty; - - if (DeploymentParameters.RuntimeFlavor == RuntimeFlavor.CoreClr && DeploymentParameters.ApplicationType == ApplicationType.Portable) - { - executableName = GetDotNetExeForArchitecture(); - executableArgs = entryPoint; - } - else - { - executableName = entryPoint; - } - - Logger.LogInformation("Executing: {exe} {args}", executableName, executableArgs); - DeploymentParameters.EnvironmentVariables["LAUNCHER_PATH"] = executableName; - DeploymentParameters.EnvironmentVariables["LAUNCHER_ARGS"] = executableArgs; - - // CurrentDirectory will point to bin/{config}/{tfm}, but the config and static files aren't copied, point to the app base instead. - Logger.LogInformation("ContentRoot: {path}", DeploymentParameters.ApplicationPath); - DeploymentParameters.EnvironmentVariables["ASPNETCORE_CONTENTROOT"] = DeploymentParameters.ApplicationPath; - } - - var testUri = TestUriHelper.BuildTestUri(ServerType.IISExpress, DeploymentParameters.ApplicationBaseUriHint); - - // Launch the host process. - var (actualUri, hostExitToken) = await StartIISExpressAsync(testUri, contentRoot, dllRoot); - - Logger.LogInformation("Application ready at URL: {appUrl}", actualUri); - - // Right now this works only for urls like http://localhost:5001/. Does not work for http://localhost:5001/subpath. - return new DeploymentResult( - LoggerFactory, - DeploymentParameters, - applicationBaseUri: actualUri.ToString(), - contentRoot: contentRoot, - hostShutdownToken: hostExitToken); - } - } - - private string CheckIfPublishIsRequired() - { - var targetFramework = DeploymentParameters.TargetFramework; - - // IISIntegration uses this layout - var dllRoot = Path.Combine(DeploymentParameters.ApplicationPath, "bin", DeploymentParameters.RuntimeArchitecture.ToString(), - DeploymentParameters.Configuration, targetFramework); - - if (!Directory.Exists(dllRoot)) - { - // Most repos use this layout - dllRoot = Path.Combine(DeploymentParameters.ApplicationPath, "bin", DeploymentParameters.Configuration, targetFramework); - - if (!Directory.Exists(dllRoot)) - { - // The bits we need weren't pre-compiled, compile on publish - DeploymentParameters.PublishApplicationBeforeDeployment = true; - } - else if (DeploymentParameters.RuntimeFlavor == RuntimeFlavor.Clr - && DeploymentParameters.RuntimeArchitecture == RuntimeArchitecture.x86) - { - // x64 is the default. Publish to rebuild for the right bitness - DeploymentParameters.PublishApplicationBeforeDeployment = true; - } - } - - if (DeploymentParameters.RuntimeFlavor == RuntimeFlavor.CoreClr - && DeploymentParameters.ApplicationType == ApplicationType.Standalone) - { - // Publish is always required to get the correct standalone files in the output directory - DeploymentParameters.PublishApplicationBeforeDeployment = true; - } - - return dllRoot; - } - - private async Task<(Uri url, CancellationToken hostExitToken)> StartIISExpressAsync(Uri uri, string contentRoot, string dllRoot) - { - using (Logger.BeginScope("StartIISExpress")) - { - var port = uri.Port; - if (port == 0) - { - port = (uri.Scheme == "https") ? TestPortHelper.GetNextSSLPort() : TestPortHelper.GetNextPort(); - } - - Logger.LogInformation("Attempting to start IIS Express on port: {port}", port); - PrepareConfig(contentRoot, dllRoot, port); - - var parameters = string.IsNullOrEmpty(DeploymentParameters.ServerConfigLocation) ? - string.Format("/port:{0} /path:\"{1}\" /trace:error /systray:false", uri.Port, contentRoot) : - string.Format("/site:{0} /config:{1} /trace:error /systray:false", DeploymentParameters.SiteName, DeploymentParameters.ServerConfigLocation); - - var iisExpressPath = GetIISExpressPath(); - - for (var attempt = 0; attempt < MaximumAttempts; attempt++) - { - Logger.LogInformation("Executing command : {iisExpress} {parameters}", iisExpressPath, parameters); - - var startInfo = new ProcessStartInfo - { - FileName = iisExpressPath, - Arguments = parameters, - UseShellExecute = false, - CreateNoWindow = true, - RedirectStandardError = true, - RedirectStandardOutput = true - }; - - AddEnvironmentVariablesToProcess(startInfo, DeploymentParameters.EnvironmentVariables); - - Uri url = null; - var started = new TaskCompletionSource(); - - var process = new Process() { StartInfo = startInfo }; - process.OutputDataReceived += (sender, dataArgs) => - { - if (string.Equals(dataArgs.Data, UnableToStartIISExpressMessage)) - { - // We completely failed to start and we don't really know why - started.TrySetException(new InvalidOperationException("Failed to start IIS Express")); - } - else if (string.Equals(dataArgs.Data, FailedToInitializeBindingsMessage)) - { - started.TrySetResult(false); - } - else if (string.Equals(dataArgs.Data, IISExpressRunningMessage)) - { - started.TrySetResult(true); - } - else if (!string.IsNullOrEmpty(dataArgs.Data)) - { - var m = UrlDetectorRegex.Match(dataArgs.Data); - if (m.Success) - { - url = new Uri(m.Groups["url"].Value); - } - } - }; - - process.EnableRaisingEvents = true; - var hostExitTokenSource = new CancellationTokenSource(); - process.Exited += (sender, e) => - { - Logger.LogInformation("iisexpress Process {pid} shut down", process.Id); - - // If TrySetResult was called above, this will just silently fail to set the new state, which is what we want - started.TrySetException(new Exception($"Command exited unexpectedly with exit code: {process.ExitCode}")); - - TriggerHostShutdown(hostExitTokenSource); - }; - process.StartAndCaptureOutAndErrToLogger("iisexpress", Logger); - Logger.LogInformation("iisexpress Process {pid} started", process.Id); - - if (process.HasExited) - { - Logger.LogError("Host process {processName} {pid} exited with code {exitCode} or failed to start.", startInfo.FileName, process.Id, process.ExitCode); - throw new Exception("Failed to start host"); - } - - // Wait for the app to start - // The timeout here is large, because we don't know how long the test could need - // We cover a lot of error cases above, but I want to make sure we eventually give up and don't hang the build - // just in case we missed one -anurse - if (!await started.Task.TimeoutAfter(TimeSpan.FromMinutes(10))) - { - Logger.LogInformation("iisexpress Process {pid} failed to bind to port {port}, trying again", process.Id, port); - - // Wait for the process to exit and try again - process.WaitForExit(30 * 1000); - await Task.Delay(1000); // Wait a second to make sure the socket is completely cleaned up - } - else - { - _hostProcess = process; - Logger.LogInformation("Started iisexpress successfully. Process Id : {processId}, Port: {port}", _hostProcess.Id, port); - return (url: url, hostExitToken: hostExitTokenSource.Token); - } - } - - var message = $"Failed to initialize IIS Express after {MaximumAttempts} attempts to select a port"; - Logger.LogError(message); - throw new TimeoutException(message); - } - } - - private void PrepareConfig(string contentRoot, string dllRoot, int port) - { - // Config is required. If not present then fall back to one we carry with us. - if (string.IsNullOrEmpty(DeploymentParameters.ServerConfigTemplateContent)) - { - using (var stream = GetType().Assembly.GetManifestResourceStream("Microsoft.AspNetCore.Server.IntegrationTesting.Http.config")) - using (var reader = new StreamReader(stream)) - { - DeploymentParameters.ServerConfigTemplateContent = reader.ReadToEnd(); - } - } - - var serverConfig = DeploymentParameters.ServerConfigTemplateContent; - - // 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, dllRoot); - serverConfig = ModifyANCMPathInConfig(replaceFlag: "[ANCMV2Path]", dllName: "aspnetcorev2.dll", serverConfig, dllRoot); - - serverConfig = ReplacePlaceholder(serverConfig, "[PORT]", port.ToString(CultureInfo.InvariantCulture)); - serverConfig = ReplacePlaceholder(serverConfig, "[ApplicationPhysicalPath]", contentRoot); - - if (DeploymentParameters.PublishApplicationBeforeDeployment) - { - // For published apps, prefer the content in the web.config, but update it. - ModifyAspNetCoreSectionInWebConfig(key: "hostingModel", - value: DeploymentParameters.HostingModel == HostingModel.InProcess ? "inprocess" : ""); - ModifyHandlerSectionInWebConfig(key: "modules", value: DeploymentParameters.AncmVersion.ToString()); - ModifyDotNetExePathInWebConfig(); - serverConfig = RemoveRedundantElements(serverConfig); - } - else - { - // The elements normally in the web.config are in the applicationhost.config for unpublished apps. - serverConfig = ReplacePlaceholder(serverConfig, "[HostingModel]", DeploymentParameters.HostingModel.ToString()); - serverConfig = ReplacePlaceholder(serverConfig, "[AspNetCoreModule]", DeploymentParameters.AncmVersion.ToString()); - } - - DeploymentParameters.ServerConfigLocation = Path.GetTempFileName(); - Logger.LogDebug("Saving Config to {configPath}", DeploymentParameters.ServerConfigLocation); - - File.WriteAllText(DeploymentParameters.ServerConfigLocation, serverConfig); - } - - private string ReplacePlaceholder(string content, string field, string value) - { - if (content.Contains(field)) - { - content = content.Replace(field, value); - Logger.LogDebug("Writing {field} '{value}' to config", field, value); - } - return content; - } - - private string ModifyANCMPathInConfig(string replaceFlag, string dllName, string serverConfig, string dllRoot) - { - 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); - } - } - - Logger.LogDebug($"Writing '{replaceFlag}' '{ancmFile}' to config"); - return serverConfig.Replace(replaceFlag, ancmFile); - } - return serverConfig; - } - - private string GetIISExpressPath() - { - var programFiles = "Program Files"; - if (DotNetCommands.IsRunningX86OnX64(DeploymentParameters.RuntimeArchitecture)) - { - programFiles = "Program Files (x86)"; - } - - // Get path to program files - var iisExpressPath = Path.Combine(Environment.GetEnvironmentVariable("SystemDrive") + "\\", programFiles, "IIS Express", "iisexpress.exe"); - - if (!File.Exists(iisExpressPath)) - { - throw new Exception("Unable to find IISExpress on the machine: " + iisExpressPath); - } - - return iisExpressPath; - } - - public override void Dispose() - { - using (Logger.BeginScope("Dispose")) - { - ShutDownIfAnyHostProcess(_hostProcess); - - if (!string.IsNullOrEmpty(DeploymentParameters.ServerConfigLocation) - && File.Exists(DeploymentParameters.ServerConfigLocation)) - { - // Delete the temp applicationHostConfig that we created. - Logger.LogDebug("Deleting applicationHost.config file from {configLocation}", DeploymentParameters.ServerConfigLocation); - try - { - File.Delete(DeploymentParameters.ServerConfigLocation); - } - catch (Exception exception) - { - // Ignore delete failures - just write a log. - Logger.LogWarning("Failed to delete '{config}'. Exception : {exception}", DeploymentParameters.ServerConfigLocation, exception.Message); - } - } - - if (DeploymentParameters.PublishApplicationBeforeDeployment) - { - CleanPublishedOutput(); - } - - InvokeUserApplicationCleanup(); - - StopTimer(); - } - - // If by this point, the host process is still running (somehow), throw an error. - // A test failure is better than a silent hang and unknown failure later on - if (_hostProcess != null && !_hostProcess.HasExited) - { - throw new Exception($"iisexpress Process {_hostProcess.Id} failed to shutdown"); - } - } - - private void ModifyDotNetExePathInWebConfig() - { - // We assume the x64 dotnet.exe is on the path so we need to provide an absolute path for x86 scenarios. - // Only do it for scenarios that rely on dotnet.exe (Core, portable, etc.). - if (DeploymentParameters.RuntimeFlavor == RuntimeFlavor.CoreClr - && DeploymentParameters.ApplicationType == ApplicationType.Portable - && DotNetCommands.IsRunningX86OnX64(DeploymentParameters.RuntimeArchitecture)) - { - var executableName = DotNetCommands.GetDotNetExecutable(DeploymentParameters.RuntimeArchitecture); - if (!File.Exists(executableName)) - { - throw new Exception($"Unable to find '{executableName}'.'"); - } - ModifyAspNetCoreSectionInWebConfig("processPath", executableName); - } - } - - // Transforms the web.config file to set attributes like hostingModel="inprocess" element - private void ModifyAspNetCoreSectionInWebConfig(string key, string value) - { - var webConfigFile = Path.Combine(DeploymentParameters.PublishedApplicationRootPath, "web.config"); - var config = XDocument.Load(webConfigFile); - var element = config.Descendants("aspNetCore").FirstOrDefault(); - element.SetAttributeValue(key, value); - config.Save(webConfigFile); - } - - private void ModifyHandlerSectionInWebConfig(string key, string value) - { - var webConfigFile = Path.Combine(DeploymentParameters.PublishedApplicationRootPath, "web.config"); - var config = XDocument.Load(webConfigFile); - var element = config.Descendants("handlers").FirstOrDefault().Descendants("add").FirstOrDefault(); - element.SetAttributeValue(key, value); - config.Save(webConfigFile); - } - - // These elements are duplicated in the web.config if you publish. Remove them from the host.config. - private string RemoveRedundantElements(string serverConfig) - { - var hostConfig = XDocument.Parse(serverConfig); - - var coreElement = hostConfig.Descendants("aspNetCore").FirstOrDefault(); - coreElement?.Remove(); - - var handlersElement = hostConfig.Descendants("handlers").First(); - var handlerElement = handlersElement.Descendants("add") - .Where(x => x.Attribute("name").Value == "aspNetCore").FirstOrDefault(); - handlerElement?.Remove(); - - return hostConfig.ToString(); - } - } -} From 65f8cfc55b3591ed52ecf6342e919c9443b9d835 Mon Sep 17 00:00:00 2001 From: Pavel Krymets Date: Tue, 17 Jul 2018 12:07:42 -0700 Subject: [PATCH 5/6] Revert "Remove IISExpress deployer (#1493)" This reverts commit ec0b7ab2cd6c351199ba7956fbc3a93d9f70923f. --- .../Common/DotNetCommands.cs | 8 +- .../Common/TestUriHelper.cs | 9 +- .../Deployers/ApplicationDeployerFactory.cs | 3 +- .../Deployers/IISExpressDeployer.cs | 436 ++++++++++++++++++ 4 files changed, 448 insertions(+), 8 deletions(-) create mode 100644 src/Microsoft.AspNetCore.Server.IntegrationTesting/Deployers/IISExpressDeployer.cs diff --git a/src/Microsoft.AspNetCore.Server.IntegrationTesting/Common/DotNetCommands.cs b/src/Microsoft.AspNetCore.Server.IntegrationTesting/Common/DotNetCommands.cs index 51414544..d9f55770 100644 --- a/src/Microsoft.AspNetCore.Server.IntegrationTesting/Common/DotNetCommands.cs +++ b/src/Microsoft.AspNetCore.Server.IntegrationTesting/Common/DotNetCommands.cs @@ -7,14 +7,14 @@ using System.Runtime.InteropServices; namespace Microsoft.AspNetCore.Server.IntegrationTesting { - public static class DotNetCommands + internal static class DotNetCommands { private const string _dotnetFolderName = ".dotnet"; internal static string DotNetHome { get; } = GetDotNetHome(); // Compare to https://github.com/aspnet/BuildTools/blob/314c98e4533217a841ff9767bb38e144eb6c93e4/tools/KoreBuild.Console/Commands/CommandContext.cs#L76 - public static string GetDotNetHome() + private static string GetDotNetHome() { var dotnetHome = Environment.GetEnvironmentVariable("DOTNET_HOME"); var userProfile = Environment.GetEnvironmentVariable("USERPROFILE"); @@ -37,7 +37,7 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting return result; } - public static string GetDotNetInstallDir(RuntimeArchitecture arch) + internal static string GetDotNetInstallDir(RuntimeArchitecture arch) { var dotnetDir = DotNetHome; if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) @@ -48,7 +48,7 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting return dotnetDir; } - public static string GetDotNetExecutable(RuntimeArchitecture arch) + internal static string GetDotNetExecutable(RuntimeArchitecture arch) { var dotnetDir = GetDotNetInstallDir(arch); diff --git a/src/Microsoft.AspNetCore.Server.IntegrationTesting/Common/TestUriHelper.cs b/src/Microsoft.AspNetCore.Server.IntegrationTesting/Common/TestUriHelper.cs index 5a79a31c..1c09b08d 100644 --- a/src/Microsoft.AspNetCore.Server.IntegrationTesting/Common/TestUriHelper.cs +++ b/src/Microsoft.AspNetCore.Server.IntegrationTesting/Common/TestUriHelper.cs @@ -2,16 +2,19 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Net; +using System.Net.Sockets; + namespace Microsoft.AspNetCore.Server.IntegrationTesting.Common { - public static class TestUriHelper + internal static class TestUriHelper { - public static Uri BuildTestUri(ServerType serverType) + internal static Uri BuildTestUri(ServerType serverType) { return BuildTestUri(serverType, hint: null); } - public static Uri BuildTestUri(ServerType serverType, string hint) + internal static Uri BuildTestUri(ServerType serverType, string hint) { // Assume status messages are enabled for Kestrel and disabled for all other servers. var statusMessagesEnabled = (serverType == ServerType.Kestrel); diff --git a/src/Microsoft.AspNetCore.Server.IntegrationTesting/Deployers/ApplicationDeployerFactory.cs b/src/Microsoft.AspNetCore.Server.IntegrationTesting/Deployers/ApplicationDeployerFactory.cs index 959f4ffe..76f3588d 100644 --- a/src/Microsoft.AspNetCore.Server.IntegrationTesting/Deployers/ApplicationDeployerFactory.cs +++ b/src/Microsoft.AspNetCore.Server.IntegrationTesting/Deployers/ApplicationDeployerFactory.cs @@ -32,8 +32,9 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting switch (deploymentParameters.ServerType) { case ServerType.IISExpress: + return new IISExpressDeployer(deploymentParameters, loggerFactory); case ServerType.IIS: - throw new NotSupportedException("Use Microsoft.AspNetCore.Server.IntegrationTesting.IIS package and IISApplicationDeployerFactory for IIS support."); + throw new NotSupportedException("The IIS deployer is no longer supported"); case ServerType.HttpSys: case ServerType.Kestrel: return new SelfHostDeployer(deploymentParameters, loggerFactory); diff --git a/src/Microsoft.AspNetCore.Server.IntegrationTesting/Deployers/IISExpressDeployer.cs b/src/Microsoft.AspNetCore.Server.IntegrationTesting/Deployers/IISExpressDeployer.cs new file mode 100644 index 00000000..8a2cc6cf --- /dev/null +++ b/src/Microsoft.AspNetCore.Server.IntegrationTesting/Deployers/IISExpressDeployer.cs @@ -0,0 +1,436 @@ +// 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.Globalization; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; +using System.Xml.Linq; +using Microsoft.AspNetCore.Server.IntegrationTesting.Common; +using Microsoft.AspNetCore.Testing; +using Microsoft.Extensions.Logging; + +namespace Microsoft.AspNetCore.Server.IntegrationTesting +{ + /// + /// Deployment helper for IISExpress. + /// + public class IISExpressDeployer : ApplicationDeployer + { + private const string IISExpressRunningMessage = "IIS Express is running."; + private const string FailedToInitializeBindingsMessage = "Failed to initialize site bindings"; + private const string UnableToStartIISExpressMessage = "Unable to start iisexpress."; + private const int MaximumAttempts = 5; + + private static readonly Regex UrlDetectorRegex = new Regex(@"^\s*Successfully registered URL ""(?[^""]+)"" for site.*$"); + + private Process _hostProcess; + + public IISExpressDeployer(DeploymentParameters deploymentParameters, ILoggerFactory loggerFactory) + : base(deploymentParameters, loggerFactory) + { + } + + public override async Task DeployAsync() + { + using (Logger.BeginScope("Deployment")) + { + // Start timer + StartTimer(); + + // For an unpublished application the dllroot points pre-built dlls like projectdir/bin/debug/net461/ + // and contentRoot points to the project directory so you get things like static assets. + // For a published app both point to the publish directory. + var dllRoot = CheckIfPublishIsRequired(); + var contentRoot = string.Empty; + if (DeploymentParameters.PublishApplicationBeforeDeployment) + { + DotnetPublish(); + contentRoot = DeploymentParameters.PublishedApplicationRootPath; + dllRoot = contentRoot; + } + else + { + // Core+Standalone always publishes. This must be Clr+Standalone or Core+Portable. + // Update processPath and arguments for our current scenario + contentRoot = DeploymentParameters.ApplicationPath; + + var executableExtension = DeploymentParameters.ApplicationType == ApplicationType.Portable ? ".dll" : ".exe"; + var entryPoint = Path.Combine(dllRoot, DeploymentParameters.ApplicationName + executableExtension); + + var executableName = string.Empty; + var executableArgs = string.Empty; + + if (DeploymentParameters.RuntimeFlavor == RuntimeFlavor.CoreClr && DeploymentParameters.ApplicationType == ApplicationType.Portable) + { + executableName = GetDotNetExeForArchitecture(); + executableArgs = entryPoint; + } + else + { + executableName = entryPoint; + } + + Logger.LogInformation("Executing: {exe} {args}", executableName, executableArgs); + DeploymentParameters.EnvironmentVariables["LAUNCHER_PATH"] = executableName; + DeploymentParameters.EnvironmentVariables["LAUNCHER_ARGS"] = executableArgs; + + // CurrentDirectory will point to bin/{config}/{tfm}, but the config and static files aren't copied, point to the app base instead. + Logger.LogInformation("ContentRoot: {path}", DeploymentParameters.ApplicationPath); + DeploymentParameters.EnvironmentVariables["ASPNETCORE_CONTENTROOT"] = DeploymentParameters.ApplicationPath; + } + + var testUri = TestUriHelper.BuildTestUri(ServerType.IISExpress, DeploymentParameters.ApplicationBaseUriHint); + + // Launch the host process. + var (actualUri, hostExitToken) = await StartIISExpressAsync(testUri, contentRoot, dllRoot); + + Logger.LogInformation("Application ready at URL: {appUrl}", actualUri); + + // Right now this works only for urls like http://localhost:5001/. Does not work for http://localhost:5001/subpath. + return new DeploymentResult( + LoggerFactory, + DeploymentParameters, + applicationBaseUri: actualUri.ToString(), + contentRoot: contentRoot, + hostShutdownToken: hostExitToken); + } + } + + private string CheckIfPublishIsRequired() + { + var targetFramework = DeploymentParameters.TargetFramework; + + // IISIntegration uses this layout + var dllRoot = Path.Combine(DeploymentParameters.ApplicationPath, "bin", DeploymentParameters.RuntimeArchitecture.ToString(), + DeploymentParameters.Configuration, targetFramework); + + if (!Directory.Exists(dllRoot)) + { + // Most repos use this layout + dllRoot = Path.Combine(DeploymentParameters.ApplicationPath, "bin", DeploymentParameters.Configuration, targetFramework); + + if (!Directory.Exists(dllRoot)) + { + // The bits we need weren't pre-compiled, compile on publish + DeploymentParameters.PublishApplicationBeforeDeployment = true; + } + else if (DeploymentParameters.RuntimeFlavor == RuntimeFlavor.Clr + && DeploymentParameters.RuntimeArchitecture == RuntimeArchitecture.x86) + { + // x64 is the default. Publish to rebuild for the right bitness + DeploymentParameters.PublishApplicationBeforeDeployment = true; + } + } + + if (DeploymentParameters.RuntimeFlavor == RuntimeFlavor.CoreClr + && DeploymentParameters.ApplicationType == ApplicationType.Standalone) + { + // Publish is always required to get the correct standalone files in the output directory + DeploymentParameters.PublishApplicationBeforeDeployment = true; + } + + return dllRoot; + } + + private async Task<(Uri url, CancellationToken hostExitToken)> StartIISExpressAsync(Uri uri, string contentRoot, string dllRoot) + { + using (Logger.BeginScope("StartIISExpress")) + { + var port = uri.Port; + if (port == 0) + { + port = (uri.Scheme == "https") ? TestPortHelper.GetNextSSLPort() : TestPortHelper.GetNextPort(); + } + + Logger.LogInformation("Attempting to start IIS Express on port: {port}", port); + PrepareConfig(contentRoot, dllRoot, port); + + var parameters = string.IsNullOrEmpty(DeploymentParameters.ServerConfigLocation) ? + string.Format("/port:{0} /path:\"{1}\" /trace:error /systray:false", uri.Port, contentRoot) : + string.Format("/site:{0} /config:{1} /trace:error /systray:false", DeploymentParameters.SiteName, DeploymentParameters.ServerConfigLocation); + + var iisExpressPath = GetIISExpressPath(); + + for (var attempt = 0; attempt < MaximumAttempts; attempt++) + { + Logger.LogInformation("Executing command : {iisExpress} {parameters}", iisExpressPath, parameters); + + var startInfo = new ProcessStartInfo + { + FileName = iisExpressPath, + Arguments = parameters, + UseShellExecute = false, + CreateNoWindow = true, + RedirectStandardError = true, + RedirectStandardOutput = true + }; + + AddEnvironmentVariablesToProcess(startInfo, DeploymentParameters.EnvironmentVariables); + + Uri url = null; + var started = new TaskCompletionSource(); + + var process = new Process() { StartInfo = startInfo }; + process.OutputDataReceived += (sender, dataArgs) => + { + if (string.Equals(dataArgs.Data, UnableToStartIISExpressMessage)) + { + // We completely failed to start and we don't really know why + started.TrySetException(new InvalidOperationException("Failed to start IIS Express")); + } + else if (string.Equals(dataArgs.Data, FailedToInitializeBindingsMessage)) + { + started.TrySetResult(false); + } + else if (string.Equals(dataArgs.Data, IISExpressRunningMessage)) + { + started.TrySetResult(true); + } + else if (!string.IsNullOrEmpty(dataArgs.Data)) + { + var m = UrlDetectorRegex.Match(dataArgs.Data); + if (m.Success) + { + url = new Uri(m.Groups["url"].Value); + } + } + }; + + process.EnableRaisingEvents = true; + var hostExitTokenSource = new CancellationTokenSource(); + process.Exited += (sender, e) => + { + Logger.LogInformation("iisexpress Process {pid} shut down", process.Id); + + // If TrySetResult was called above, this will just silently fail to set the new state, which is what we want + started.TrySetException(new Exception($"Command exited unexpectedly with exit code: {process.ExitCode}")); + + TriggerHostShutdown(hostExitTokenSource); + }; + process.StartAndCaptureOutAndErrToLogger("iisexpress", Logger); + Logger.LogInformation("iisexpress Process {pid} started", process.Id); + + if (process.HasExited) + { + Logger.LogError("Host process {processName} {pid} exited with code {exitCode} or failed to start.", startInfo.FileName, process.Id, process.ExitCode); + throw new Exception("Failed to start host"); + } + + // Wait for the app to start + // The timeout here is large, because we don't know how long the test could need + // We cover a lot of error cases above, but I want to make sure we eventually give up and don't hang the build + // just in case we missed one -anurse + if (!await started.Task.TimeoutAfter(TimeSpan.FromMinutes(10))) + { + Logger.LogInformation("iisexpress Process {pid} failed to bind to port {port}, trying again", process.Id, port); + + // Wait for the process to exit and try again + process.WaitForExit(30 * 1000); + await Task.Delay(1000); // Wait a second to make sure the socket is completely cleaned up + } + else + { + _hostProcess = process; + Logger.LogInformation("Started iisexpress successfully. Process Id : {processId}, Port: {port}", _hostProcess.Id, port); + return (url: url, hostExitToken: hostExitTokenSource.Token); + } + } + + var message = $"Failed to initialize IIS Express after {MaximumAttempts} attempts to select a port"; + Logger.LogError(message); + throw new TimeoutException(message); + } + } + + private void PrepareConfig(string contentRoot, string dllRoot, int port) + { + // Config is required. If not present then fall back to one we carry with us. + if (string.IsNullOrEmpty(DeploymentParameters.ServerConfigTemplateContent)) + { + using (var stream = GetType().Assembly.GetManifestResourceStream("Microsoft.AspNetCore.Server.IntegrationTesting.Http.config")) + using (var reader = new StreamReader(stream)) + { + DeploymentParameters.ServerConfigTemplateContent = reader.ReadToEnd(); + } + } + + var serverConfig = DeploymentParameters.ServerConfigTemplateContent; + + // 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, dllRoot); + serverConfig = ModifyANCMPathInConfig(replaceFlag: "[ANCMV2Path]", dllName: "aspnetcorev2.dll", serverConfig, dllRoot); + + serverConfig = ReplacePlaceholder(serverConfig, "[PORT]", port.ToString(CultureInfo.InvariantCulture)); + serverConfig = ReplacePlaceholder(serverConfig, "[ApplicationPhysicalPath]", contentRoot); + + if (DeploymentParameters.PublishApplicationBeforeDeployment) + { + // For published apps, prefer the content in the web.config, but update it. + ModifyAspNetCoreSectionInWebConfig(key: "hostingModel", + value: DeploymentParameters.HostingModel == HostingModel.InProcess ? "inprocess" : ""); + ModifyHandlerSectionInWebConfig(key: "modules", value: DeploymentParameters.AncmVersion.ToString()); + ModifyDotNetExePathInWebConfig(); + serverConfig = RemoveRedundantElements(serverConfig); + } + else + { + // The elements normally in the web.config are in the applicationhost.config for unpublished apps. + serverConfig = ReplacePlaceholder(serverConfig, "[HostingModel]", DeploymentParameters.HostingModel.ToString()); + serverConfig = ReplacePlaceholder(serverConfig, "[AspNetCoreModule]", DeploymentParameters.AncmVersion.ToString()); + } + + DeploymentParameters.ServerConfigLocation = Path.GetTempFileName(); + Logger.LogDebug("Saving Config to {configPath}", DeploymentParameters.ServerConfigLocation); + + File.WriteAllText(DeploymentParameters.ServerConfigLocation, serverConfig); + } + + private string ReplacePlaceholder(string content, string field, string value) + { + if (content.Contains(field)) + { + content = content.Replace(field, value); + Logger.LogDebug("Writing {field} '{value}' to config", field, value); + } + return content; + } + + private string ModifyANCMPathInConfig(string replaceFlag, string dllName, string serverConfig, string dllRoot) + { + 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); + } + } + + Logger.LogDebug($"Writing '{replaceFlag}' '{ancmFile}' to config"); + return serverConfig.Replace(replaceFlag, ancmFile); + } + return serverConfig; + } + + private string GetIISExpressPath() + { + var programFiles = "Program Files"; + if (DotNetCommands.IsRunningX86OnX64(DeploymentParameters.RuntimeArchitecture)) + { + programFiles = "Program Files (x86)"; + } + + // Get path to program files + var iisExpressPath = Path.Combine(Environment.GetEnvironmentVariable("SystemDrive") + "\\", programFiles, "IIS Express", "iisexpress.exe"); + + if (!File.Exists(iisExpressPath)) + { + throw new Exception("Unable to find IISExpress on the machine: " + iisExpressPath); + } + + return iisExpressPath; + } + + public override void Dispose() + { + using (Logger.BeginScope("Dispose")) + { + ShutDownIfAnyHostProcess(_hostProcess); + + if (!string.IsNullOrEmpty(DeploymentParameters.ServerConfigLocation) + && File.Exists(DeploymentParameters.ServerConfigLocation)) + { + // Delete the temp applicationHostConfig that we created. + Logger.LogDebug("Deleting applicationHost.config file from {configLocation}", DeploymentParameters.ServerConfigLocation); + try + { + File.Delete(DeploymentParameters.ServerConfigLocation); + } + catch (Exception exception) + { + // Ignore delete failures - just write a log. + Logger.LogWarning("Failed to delete '{config}'. Exception : {exception}", DeploymentParameters.ServerConfigLocation, exception.Message); + } + } + + if (DeploymentParameters.PublishApplicationBeforeDeployment) + { + CleanPublishedOutput(); + } + + InvokeUserApplicationCleanup(); + + StopTimer(); + } + + // If by this point, the host process is still running (somehow), throw an error. + // A test failure is better than a silent hang and unknown failure later on + if (_hostProcess != null && !_hostProcess.HasExited) + { + throw new Exception($"iisexpress Process {_hostProcess.Id} failed to shutdown"); + } + } + + private void ModifyDotNetExePathInWebConfig() + { + // We assume the x64 dotnet.exe is on the path so we need to provide an absolute path for x86 scenarios. + // Only do it for scenarios that rely on dotnet.exe (Core, portable, etc.). + if (DeploymentParameters.RuntimeFlavor == RuntimeFlavor.CoreClr + && DeploymentParameters.ApplicationType == ApplicationType.Portable + && DotNetCommands.IsRunningX86OnX64(DeploymentParameters.RuntimeArchitecture)) + { + var executableName = DotNetCommands.GetDotNetExecutable(DeploymentParameters.RuntimeArchitecture); + if (!File.Exists(executableName)) + { + throw new Exception($"Unable to find '{executableName}'.'"); + } + ModifyAspNetCoreSectionInWebConfig("processPath", executableName); + } + } + + // Transforms the web.config file to set attributes like hostingModel="inprocess" element + private void ModifyAspNetCoreSectionInWebConfig(string key, string value) + { + var webConfigFile = Path.Combine(DeploymentParameters.PublishedApplicationRootPath, "web.config"); + var config = XDocument.Load(webConfigFile); + var element = config.Descendants("aspNetCore").FirstOrDefault(); + element.SetAttributeValue(key, value); + config.Save(webConfigFile); + } + + private void ModifyHandlerSectionInWebConfig(string key, string value) + { + var webConfigFile = Path.Combine(DeploymentParameters.PublishedApplicationRootPath, "web.config"); + var config = XDocument.Load(webConfigFile); + var element = config.Descendants("handlers").FirstOrDefault().Descendants("add").FirstOrDefault(); + element.SetAttributeValue(key, value); + config.Save(webConfigFile); + } + + // These elements are duplicated in the web.config if you publish. Remove them from the host.config. + private string RemoveRedundantElements(string serverConfig) + { + var hostConfig = XDocument.Parse(serverConfig); + + var coreElement = hostConfig.Descendants("aspNetCore").FirstOrDefault(); + coreElement?.Remove(); + + var handlersElement = hostConfig.Descendants("handlers").First(); + var handlerElement = handlersElement.Descendants("add") + .Where(x => x.Attribute("name").Value == "aspNetCore").FirstOrDefault(); + handlerElement?.Remove(); + + return hostConfig.ToString(); + } + } +} From 19839ed3085dfde64fc93675efdcdfb8fbe87b11 Mon Sep 17 00:00:00 2001 From: Pavel Krymets Date: Tue, 17 Jul 2018 21:05:59 -0700 Subject: [PATCH 6/6] Revert "Merge pull request #1494 from aspnet/revert-1493-pakrym/useiis" (#1496) This reverts commit 7a0a1ee50bb426323d4209707df98bf77fbabfa1, reversing changes made to ec0b7ab2cd6c351199ba7956fbc3a93d9f70923f. --- .../Common/DotNetCommands.cs | 10 +- .../Common/TestUriHelper.cs | 9 +- .../Deployers/ApplicationDeployerFactory.cs | 3 +- .../Deployers/IISExpressDeployer.cs | 436 ------------------ 4 files changed, 9 insertions(+), 449 deletions(-) delete mode 100644 src/Microsoft.AspNetCore.Server.IntegrationTesting/Deployers/IISExpressDeployer.cs diff --git a/src/Microsoft.AspNetCore.Server.IntegrationTesting/Common/DotNetCommands.cs b/src/Microsoft.AspNetCore.Server.IntegrationTesting/Common/DotNetCommands.cs index d9f55770..e30e9def 100644 --- a/src/Microsoft.AspNetCore.Server.IntegrationTesting/Common/DotNetCommands.cs +++ b/src/Microsoft.AspNetCore.Server.IntegrationTesting/Common/DotNetCommands.cs @@ -7,14 +7,14 @@ using System.Runtime.InteropServices; namespace Microsoft.AspNetCore.Server.IntegrationTesting { - internal static class DotNetCommands + public static class DotNetCommands { private const string _dotnetFolderName = ".dotnet"; internal static string DotNetHome { get; } = GetDotNetHome(); // Compare to https://github.com/aspnet/BuildTools/blob/314c98e4533217a841ff9767bb38e144eb6c93e4/tools/KoreBuild.Console/Commands/CommandContext.cs#L76 - private static string GetDotNetHome() + public static string GetDotNetHome() { var dotnetHome = Environment.GetEnvironmentVariable("DOTNET_HOME"); var userProfile = Environment.GetEnvironmentVariable("USERPROFILE"); @@ -37,7 +37,7 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting return result; } - internal static string GetDotNetInstallDir(RuntimeArchitecture arch) + public static string GetDotNetInstallDir(RuntimeArchitecture arch) { var dotnetDir = DotNetHome; if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) @@ -48,7 +48,7 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting return dotnetDir; } - internal static string GetDotNetExecutable(RuntimeArchitecture arch) + public static string GetDotNetExecutable(RuntimeArchitecture arch) { var dotnetDir = GetDotNetInstallDir(arch); @@ -62,7 +62,7 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting return Path.Combine(dotnetDir, dotnetFile); } - internal static bool IsRunningX86OnX64(RuntimeArchitecture arch) + public static bool IsRunningX86OnX64(RuntimeArchitecture arch) { return (RuntimeInformation.OSArchitecture == Architecture.X64 || RuntimeInformation.OSArchitecture == Architecture.Arm64) && arch == RuntimeArchitecture.x86; diff --git a/src/Microsoft.AspNetCore.Server.IntegrationTesting/Common/TestUriHelper.cs b/src/Microsoft.AspNetCore.Server.IntegrationTesting/Common/TestUriHelper.cs index 1c09b08d..5a79a31c 100644 --- a/src/Microsoft.AspNetCore.Server.IntegrationTesting/Common/TestUriHelper.cs +++ b/src/Microsoft.AspNetCore.Server.IntegrationTesting/Common/TestUriHelper.cs @@ -2,19 +2,16 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Net; -using System.Net.Sockets; - namespace Microsoft.AspNetCore.Server.IntegrationTesting.Common { - internal static class TestUriHelper + public static class TestUriHelper { - internal static Uri BuildTestUri(ServerType serverType) + public static Uri BuildTestUri(ServerType serverType) { return BuildTestUri(serverType, hint: null); } - internal static Uri BuildTestUri(ServerType serverType, string hint) + public static Uri BuildTestUri(ServerType serverType, string hint) { // Assume status messages are enabled for Kestrel and disabled for all other servers. var statusMessagesEnabled = (serverType == ServerType.Kestrel); diff --git a/src/Microsoft.AspNetCore.Server.IntegrationTesting/Deployers/ApplicationDeployerFactory.cs b/src/Microsoft.AspNetCore.Server.IntegrationTesting/Deployers/ApplicationDeployerFactory.cs index 76f3588d..959f4ffe 100644 --- a/src/Microsoft.AspNetCore.Server.IntegrationTesting/Deployers/ApplicationDeployerFactory.cs +++ b/src/Microsoft.AspNetCore.Server.IntegrationTesting/Deployers/ApplicationDeployerFactory.cs @@ -32,9 +32,8 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting switch (deploymentParameters.ServerType) { case ServerType.IISExpress: - return new IISExpressDeployer(deploymentParameters, loggerFactory); case ServerType.IIS: - throw new NotSupportedException("The IIS deployer is no longer supported"); + throw new NotSupportedException("Use Microsoft.AspNetCore.Server.IntegrationTesting.IIS package and IISApplicationDeployerFactory for IIS support."); case ServerType.HttpSys: case ServerType.Kestrel: return new SelfHostDeployer(deploymentParameters, loggerFactory); diff --git a/src/Microsoft.AspNetCore.Server.IntegrationTesting/Deployers/IISExpressDeployer.cs b/src/Microsoft.AspNetCore.Server.IntegrationTesting/Deployers/IISExpressDeployer.cs deleted file mode 100644 index 8a2cc6cf..00000000 --- a/src/Microsoft.AspNetCore.Server.IntegrationTesting/Deployers/IISExpressDeployer.cs +++ /dev/null @@ -1,436 +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.Globalization; -using System.IO; -using System.Linq; -using System.Text.RegularExpressions; -using System.Threading; -using System.Threading.Tasks; -using System.Xml.Linq; -using Microsoft.AspNetCore.Server.IntegrationTesting.Common; -using Microsoft.AspNetCore.Testing; -using Microsoft.Extensions.Logging; - -namespace Microsoft.AspNetCore.Server.IntegrationTesting -{ - /// - /// Deployment helper for IISExpress. - /// - public class IISExpressDeployer : ApplicationDeployer - { - private const string IISExpressRunningMessage = "IIS Express is running."; - private const string FailedToInitializeBindingsMessage = "Failed to initialize site bindings"; - private const string UnableToStartIISExpressMessage = "Unable to start iisexpress."; - private const int MaximumAttempts = 5; - - private static readonly Regex UrlDetectorRegex = new Regex(@"^\s*Successfully registered URL ""(?[^""]+)"" for site.*$"); - - private Process _hostProcess; - - public IISExpressDeployer(DeploymentParameters deploymentParameters, ILoggerFactory loggerFactory) - : base(deploymentParameters, loggerFactory) - { - } - - public override async Task DeployAsync() - { - using (Logger.BeginScope("Deployment")) - { - // Start timer - StartTimer(); - - // For an unpublished application the dllroot points pre-built dlls like projectdir/bin/debug/net461/ - // and contentRoot points to the project directory so you get things like static assets. - // For a published app both point to the publish directory. - var dllRoot = CheckIfPublishIsRequired(); - var contentRoot = string.Empty; - if (DeploymentParameters.PublishApplicationBeforeDeployment) - { - DotnetPublish(); - contentRoot = DeploymentParameters.PublishedApplicationRootPath; - dllRoot = contentRoot; - } - else - { - // Core+Standalone always publishes. This must be Clr+Standalone or Core+Portable. - // Update processPath and arguments for our current scenario - contentRoot = DeploymentParameters.ApplicationPath; - - var executableExtension = DeploymentParameters.ApplicationType == ApplicationType.Portable ? ".dll" : ".exe"; - var entryPoint = Path.Combine(dllRoot, DeploymentParameters.ApplicationName + executableExtension); - - var executableName = string.Empty; - var executableArgs = string.Empty; - - if (DeploymentParameters.RuntimeFlavor == RuntimeFlavor.CoreClr && DeploymentParameters.ApplicationType == ApplicationType.Portable) - { - executableName = GetDotNetExeForArchitecture(); - executableArgs = entryPoint; - } - else - { - executableName = entryPoint; - } - - Logger.LogInformation("Executing: {exe} {args}", executableName, executableArgs); - DeploymentParameters.EnvironmentVariables["LAUNCHER_PATH"] = executableName; - DeploymentParameters.EnvironmentVariables["LAUNCHER_ARGS"] = executableArgs; - - // CurrentDirectory will point to bin/{config}/{tfm}, but the config and static files aren't copied, point to the app base instead. - Logger.LogInformation("ContentRoot: {path}", DeploymentParameters.ApplicationPath); - DeploymentParameters.EnvironmentVariables["ASPNETCORE_CONTENTROOT"] = DeploymentParameters.ApplicationPath; - } - - var testUri = TestUriHelper.BuildTestUri(ServerType.IISExpress, DeploymentParameters.ApplicationBaseUriHint); - - // Launch the host process. - var (actualUri, hostExitToken) = await StartIISExpressAsync(testUri, contentRoot, dllRoot); - - Logger.LogInformation("Application ready at URL: {appUrl}", actualUri); - - // Right now this works only for urls like http://localhost:5001/. Does not work for http://localhost:5001/subpath. - return new DeploymentResult( - LoggerFactory, - DeploymentParameters, - applicationBaseUri: actualUri.ToString(), - contentRoot: contentRoot, - hostShutdownToken: hostExitToken); - } - } - - private string CheckIfPublishIsRequired() - { - var targetFramework = DeploymentParameters.TargetFramework; - - // IISIntegration uses this layout - var dllRoot = Path.Combine(DeploymentParameters.ApplicationPath, "bin", DeploymentParameters.RuntimeArchitecture.ToString(), - DeploymentParameters.Configuration, targetFramework); - - if (!Directory.Exists(dllRoot)) - { - // Most repos use this layout - dllRoot = Path.Combine(DeploymentParameters.ApplicationPath, "bin", DeploymentParameters.Configuration, targetFramework); - - if (!Directory.Exists(dllRoot)) - { - // The bits we need weren't pre-compiled, compile on publish - DeploymentParameters.PublishApplicationBeforeDeployment = true; - } - else if (DeploymentParameters.RuntimeFlavor == RuntimeFlavor.Clr - && DeploymentParameters.RuntimeArchitecture == RuntimeArchitecture.x86) - { - // x64 is the default. Publish to rebuild for the right bitness - DeploymentParameters.PublishApplicationBeforeDeployment = true; - } - } - - if (DeploymentParameters.RuntimeFlavor == RuntimeFlavor.CoreClr - && DeploymentParameters.ApplicationType == ApplicationType.Standalone) - { - // Publish is always required to get the correct standalone files in the output directory - DeploymentParameters.PublishApplicationBeforeDeployment = true; - } - - return dllRoot; - } - - private async Task<(Uri url, CancellationToken hostExitToken)> StartIISExpressAsync(Uri uri, string contentRoot, string dllRoot) - { - using (Logger.BeginScope("StartIISExpress")) - { - var port = uri.Port; - if (port == 0) - { - port = (uri.Scheme == "https") ? TestPortHelper.GetNextSSLPort() : TestPortHelper.GetNextPort(); - } - - Logger.LogInformation("Attempting to start IIS Express on port: {port}", port); - PrepareConfig(contentRoot, dllRoot, port); - - var parameters = string.IsNullOrEmpty(DeploymentParameters.ServerConfigLocation) ? - string.Format("/port:{0} /path:\"{1}\" /trace:error /systray:false", uri.Port, contentRoot) : - string.Format("/site:{0} /config:{1} /trace:error /systray:false", DeploymentParameters.SiteName, DeploymentParameters.ServerConfigLocation); - - var iisExpressPath = GetIISExpressPath(); - - for (var attempt = 0; attempt < MaximumAttempts; attempt++) - { - Logger.LogInformation("Executing command : {iisExpress} {parameters}", iisExpressPath, parameters); - - var startInfo = new ProcessStartInfo - { - FileName = iisExpressPath, - Arguments = parameters, - UseShellExecute = false, - CreateNoWindow = true, - RedirectStandardError = true, - RedirectStandardOutput = true - }; - - AddEnvironmentVariablesToProcess(startInfo, DeploymentParameters.EnvironmentVariables); - - Uri url = null; - var started = new TaskCompletionSource(); - - var process = new Process() { StartInfo = startInfo }; - process.OutputDataReceived += (sender, dataArgs) => - { - if (string.Equals(dataArgs.Data, UnableToStartIISExpressMessage)) - { - // We completely failed to start and we don't really know why - started.TrySetException(new InvalidOperationException("Failed to start IIS Express")); - } - else if (string.Equals(dataArgs.Data, FailedToInitializeBindingsMessage)) - { - started.TrySetResult(false); - } - else if (string.Equals(dataArgs.Data, IISExpressRunningMessage)) - { - started.TrySetResult(true); - } - else if (!string.IsNullOrEmpty(dataArgs.Data)) - { - var m = UrlDetectorRegex.Match(dataArgs.Data); - if (m.Success) - { - url = new Uri(m.Groups["url"].Value); - } - } - }; - - process.EnableRaisingEvents = true; - var hostExitTokenSource = new CancellationTokenSource(); - process.Exited += (sender, e) => - { - Logger.LogInformation("iisexpress Process {pid} shut down", process.Id); - - // If TrySetResult was called above, this will just silently fail to set the new state, which is what we want - started.TrySetException(new Exception($"Command exited unexpectedly with exit code: {process.ExitCode}")); - - TriggerHostShutdown(hostExitTokenSource); - }; - process.StartAndCaptureOutAndErrToLogger("iisexpress", Logger); - Logger.LogInformation("iisexpress Process {pid} started", process.Id); - - if (process.HasExited) - { - Logger.LogError("Host process {processName} {pid} exited with code {exitCode} or failed to start.", startInfo.FileName, process.Id, process.ExitCode); - throw new Exception("Failed to start host"); - } - - // Wait for the app to start - // The timeout here is large, because we don't know how long the test could need - // We cover a lot of error cases above, but I want to make sure we eventually give up and don't hang the build - // just in case we missed one -anurse - if (!await started.Task.TimeoutAfter(TimeSpan.FromMinutes(10))) - { - Logger.LogInformation("iisexpress Process {pid} failed to bind to port {port}, trying again", process.Id, port); - - // Wait for the process to exit and try again - process.WaitForExit(30 * 1000); - await Task.Delay(1000); // Wait a second to make sure the socket is completely cleaned up - } - else - { - _hostProcess = process; - Logger.LogInformation("Started iisexpress successfully. Process Id : {processId}, Port: {port}", _hostProcess.Id, port); - return (url: url, hostExitToken: hostExitTokenSource.Token); - } - } - - var message = $"Failed to initialize IIS Express after {MaximumAttempts} attempts to select a port"; - Logger.LogError(message); - throw new TimeoutException(message); - } - } - - private void PrepareConfig(string contentRoot, string dllRoot, int port) - { - // Config is required. If not present then fall back to one we carry with us. - if (string.IsNullOrEmpty(DeploymentParameters.ServerConfigTemplateContent)) - { - using (var stream = GetType().Assembly.GetManifestResourceStream("Microsoft.AspNetCore.Server.IntegrationTesting.Http.config")) - using (var reader = new StreamReader(stream)) - { - DeploymentParameters.ServerConfigTemplateContent = reader.ReadToEnd(); - } - } - - var serverConfig = DeploymentParameters.ServerConfigTemplateContent; - - // 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, dllRoot); - serverConfig = ModifyANCMPathInConfig(replaceFlag: "[ANCMV2Path]", dllName: "aspnetcorev2.dll", serverConfig, dllRoot); - - serverConfig = ReplacePlaceholder(serverConfig, "[PORT]", port.ToString(CultureInfo.InvariantCulture)); - serverConfig = ReplacePlaceholder(serverConfig, "[ApplicationPhysicalPath]", contentRoot); - - if (DeploymentParameters.PublishApplicationBeforeDeployment) - { - // For published apps, prefer the content in the web.config, but update it. - ModifyAspNetCoreSectionInWebConfig(key: "hostingModel", - value: DeploymentParameters.HostingModel == HostingModel.InProcess ? "inprocess" : ""); - ModifyHandlerSectionInWebConfig(key: "modules", value: DeploymentParameters.AncmVersion.ToString()); - ModifyDotNetExePathInWebConfig(); - serverConfig = RemoveRedundantElements(serverConfig); - } - else - { - // The elements normally in the web.config are in the applicationhost.config for unpublished apps. - serverConfig = ReplacePlaceholder(serverConfig, "[HostingModel]", DeploymentParameters.HostingModel.ToString()); - serverConfig = ReplacePlaceholder(serverConfig, "[AspNetCoreModule]", DeploymentParameters.AncmVersion.ToString()); - } - - DeploymentParameters.ServerConfigLocation = Path.GetTempFileName(); - Logger.LogDebug("Saving Config to {configPath}", DeploymentParameters.ServerConfigLocation); - - File.WriteAllText(DeploymentParameters.ServerConfigLocation, serverConfig); - } - - private string ReplacePlaceholder(string content, string field, string value) - { - if (content.Contains(field)) - { - content = content.Replace(field, value); - Logger.LogDebug("Writing {field} '{value}' to config", field, value); - } - return content; - } - - private string ModifyANCMPathInConfig(string replaceFlag, string dllName, string serverConfig, string dllRoot) - { - 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); - } - } - - Logger.LogDebug($"Writing '{replaceFlag}' '{ancmFile}' to config"); - return serverConfig.Replace(replaceFlag, ancmFile); - } - return serverConfig; - } - - private string GetIISExpressPath() - { - var programFiles = "Program Files"; - if (DotNetCommands.IsRunningX86OnX64(DeploymentParameters.RuntimeArchitecture)) - { - programFiles = "Program Files (x86)"; - } - - // Get path to program files - var iisExpressPath = Path.Combine(Environment.GetEnvironmentVariable("SystemDrive") + "\\", programFiles, "IIS Express", "iisexpress.exe"); - - if (!File.Exists(iisExpressPath)) - { - throw new Exception("Unable to find IISExpress on the machine: " + iisExpressPath); - } - - return iisExpressPath; - } - - public override void Dispose() - { - using (Logger.BeginScope("Dispose")) - { - ShutDownIfAnyHostProcess(_hostProcess); - - if (!string.IsNullOrEmpty(DeploymentParameters.ServerConfigLocation) - && File.Exists(DeploymentParameters.ServerConfigLocation)) - { - // Delete the temp applicationHostConfig that we created. - Logger.LogDebug("Deleting applicationHost.config file from {configLocation}", DeploymentParameters.ServerConfigLocation); - try - { - File.Delete(DeploymentParameters.ServerConfigLocation); - } - catch (Exception exception) - { - // Ignore delete failures - just write a log. - Logger.LogWarning("Failed to delete '{config}'. Exception : {exception}", DeploymentParameters.ServerConfigLocation, exception.Message); - } - } - - if (DeploymentParameters.PublishApplicationBeforeDeployment) - { - CleanPublishedOutput(); - } - - InvokeUserApplicationCleanup(); - - StopTimer(); - } - - // If by this point, the host process is still running (somehow), throw an error. - // A test failure is better than a silent hang and unknown failure later on - if (_hostProcess != null && !_hostProcess.HasExited) - { - throw new Exception($"iisexpress Process {_hostProcess.Id} failed to shutdown"); - } - } - - private void ModifyDotNetExePathInWebConfig() - { - // We assume the x64 dotnet.exe is on the path so we need to provide an absolute path for x86 scenarios. - // Only do it for scenarios that rely on dotnet.exe (Core, portable, etc.). - if (DeploymentParameters.RuntimeFlavor == RuntimeFlavor.CoreClr - && DeploymentParameters.ApplicationType == ApplicationType.Portable - && DotNetCommands.IsRunningX86OnX64(DeploymentParameters.RuntimeArchitecture)) - { - var executableName = DotNetCommands.GetDotNetExecutable(DeploymentParameters.RuntimeArchitecture); - if (!File.Exists(executableName)) - { - throw new Exception($"Unable to find '{executableName}'.'"); - } - ModifyAspNetCoreSectionInWebConfig("processPath", executableName); - } - } - - // Transforms the web.config file to set attributes like hostingModel="inprocess" element - private void ModifyAspNetCoreSectionInWebConfig(string key, string value) - { - var webConfigFile = Path.Combine(DeploymentParameters.PublishedApplicationRootPath, "web.config"); - var config = XDocument.Load(webConfigFile); - var element = config.Descendants("aspNetCore").FirstOrDefault(); - element.SetAttributeValue(key, value); - config.Save(webConfigFile); - } - - private void ModifyHandlerSectionInWebConfig(string key, string value) - { - var webConfigFile = Path.Combine(DeploymentParameters.PublishedApplicationRootPath, "web.config"); - var config = XDocument.Load(webConfigFile); - var element = config.Descendants("handlers").FirstOrDefault().Descendants("add").FirstOrDefault(); - element.SetAttributeValue(key, value); - config.Save(webConfigFile); - } - - // These elements are duplicated in the web.config if you publish. Remove them from the host.config. - private string RemoveRedundantElements(string serverConfig) - { - var hostConfig = XDocument.Parse(serverConfig); - - var coreElement = hostConfig.Descendants("aspNetCore").FirstOrDefault(); - coreElement?.Remove(); - - var handlersElement = hostConfig.Descendants("handlers").First(); - var handlerElement = handlersElement.Descendants("add") - .Where(x => x.Attribute("name").Value == "aspNetCore").FirstOrDefault(); - handlerElement?.Remove(); - - return hostConfig.ToString(); - } - } -}