diff --git a/IISIntegration.sln b/IISIntegration.sln index 6548ea89c5..4ac18b81bd 100644 --- a/IISIntegration.sln +++ b/IISIntegration.sln @@ -30,6 +30,10 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNetCore.Server EndProject Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNetCore.Server.IISIntegration.Tests", "test\Microsoft.AspNetCore.Server.IISIntegration.Tests\Microsoft.AspNetCore.Server.IISIntegration.Tests.xproj", "{4106DB10-E09F-480E-9CE6-B39235512EE6}" EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNetCore.Server.IISIntegration.Tools.Tests", "test\Microsoft.AspNetCore.Server.IISIntegration.Tools.Tests\Microsoft.AspNetCore.Server.IISIntegration.Tools.Tests.xproj", "{D0FA003D-DE4C-480E-B4A4-BD38691B36AD}" +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNetCore.Server.IISIntegration.Tools", "src\Microsoft.AspNetCore.Server.IISIntegration.Tools\Microsoft.AspNetCore.Server.IISIntegration.Tools.xproj", "{B1BC61B7-BA1D-4100-A2E8-49D00CE2771D}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -68,6 +72,14 @@ Global {4106DB10-E09F-480E-9CE6-B39235512EE6}.Debug|Any CPU.Build.0 = Debug|Any CPU {4106DB10-E09F-480E-9CE6-B39235512EE6}.Release|Any CPU.ActiveCfg = Release|Any CPU {4106DB10-E09F-480E-9CE6-B39235512EE6}.Release|Any CPU.Build.0 = Release|Any CPU + {D0FA003D-DE4C-480E-B4A4-BD38691B36AD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D0FA003D-DE4C-480E-B4A4-BD38691B36AD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D0FA003D-DE4C-480E-B4A4-BD38691B36AD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D0FA003D-DE4C-480E-B4A4-BD38691B36AD}.Release|Any CPU.Build.0 = Release|Any CPU + {B1BC61B7-BA1D-4100-A2E8-49D00CE2771D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B1BC61B7-BA1D-4100-A2E8-49D00CE2771D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B1BC61B7-BA1D-4100-A2E8-49D00CE2771D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B1BC61B7-BA1D-4100-A2E8-49D00CE2771D}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -81,5 +93,7 @@ Global {8B3446E8-E6A8-4591-AA63-A95837C6E97C} = {04B1EDB6-E967-4D25-89B9-E6F8304038CD} {7F2F50C7-610F-4B69-B945-CA283511A587} = {EF30B533-D715-421A-92B7-92FEF460AC9C} {4106DB10-E09F-480E-9CE6-B39235512EE6} = {EF30B533-D715-421A-92B7-92FEF460AC9C} + {D0FA003D-DE4C-480E-B4A4-BD38691B36AD} = {EF30B533-D715-421A-92B7-92FEF460AC9C} + {B1BC61B7-BA1D-4100-A2E8-49D00CE2771D} = {04B1EDB6-E967-4D25-89B9-E6F8304038CD} EndGlobalSection EndGlobal diff --git a/NuGetPackageVerifier.json b/NuGetPackageVerifier.json index 22c9f65529..1b75677d04 100644 --- a/NuGetPackageVerifier.json +++ b/NuGetPackageVerifier.json @@ -6,7 +6,8 @@ "packages": { "dotnet-publish-iis": { }, "Microsoft.AspNetCore.IISPlatformHandler": { }, - "Microsoft.AspNetCore.Server.IISIntegration": { } + "Microsoft.AspNetCore.Server.IISIntegration": { }, + "Microsoft.AspNetCore.Server.IISIntegration.Tools": { } } }, "Default": { // Rules to run for packages not listed in any other set. diff --git a/src/Microsoft.AspNetCore.Server.IISIntegration.Tools/Microsoft.AspNetCore.Server.IISIntegration.Tools.xproj b/src/Microsoft.AspNetCore.Server.IISIntegration.Tools/Microsoft.AspNetCore.Server.IISIntegration.Tools.xproj new file mode 100644 index 0000000000..7c005d8d4a --- /dev/null +++ b/src/Microsoft.AspNetCore.Server.IISIntegration.Tools/Microsoft.AspNetCore.Server.IISIntegration.Tools.xproj @@ -0,0 +1,19 @@ + + + + 14.0.24720 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + b1bc61b7-ba1d-4100-a2e8-49d00ce2771d + Microsoft.AspNetCore.Server.IISIntegration.Tools + ..\..\artifacts\obj\$(MSBuildProjectName) + ..\..\artifacts\bin\$(MSBuildProjectName)\ + + + + 2.0 + + + \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Server.IISIntegration.Tools/Program.cs b/src/Microsoft.AspNetCore.Server.IISIntegration.Tools/Program.cs new file mode 100644 index 0000000000..c279b3bb7b --- /dev/null +++ b/src/Microsoft.AspNetCore.Server.IISIntegration.Tools/Program.cs @@ -0,0 +1,58 @@ +// 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 Microsoft.DotNet.Cli.Utils; +using Microsoft.Extensions.CommandLineUtils; + +namespace Microsoft.AspNetCore.Tools.PublishIIS +{ + public class Microsoft.AspNetCore.Server.IISIntegration.Tools + { + public static int Main(string[] args) + { + var app = new CommandLineApplication + { + Name = "dotnet publish-iis", + FullName = "Asp.Net IIS Publisher", + Description = "IIS Publisher for the Asp.Net web applications", + }; + app.HelpOption("-h|--help"); + + var publishFolderOption = app.Option("--publish-folder|-p", "The path to the publish output folder", CommandOptionType.SingleValue); + var webRootOption = app.Option("--webroot|-w", "The name of webroot folder", CommandOptionType.SingleValue); + var projectPath = app.Argument("", "The path to the project (project folder or project.json) being published. If empty the current directory is used."); + + app.OnExecute(() => + { + var publishFolder = publishFolderOption.Value(); + + if (publishFolder == null) + { + app.ShowHelp(); + return 2; + } + + Reporter.Output.WriteLine($"Configuring the following project for use with IIS: '{publishFolder}'"); + + var exitCode = new PublishIISCommand(publishFolder, projectPath.Value, webRootOption.Value()).Run(); + + Reporter.Output.WriteLine("Configuring project completed successfully"); + + return exitCode; + }); + + try + { + return app.Execute(args); + } + catch (Exception e) + { + Reporter.Error.WriteLine(e.Message.Red()); + Reporter.Verbose.WriteLine(e.ToString().Yellow()); + } + + return 1; + } + } +} diff --git a/src/Microsoft.AspNetCore.Server.IISIntegration.Tools/Properties/AssemblyInfo.cs b/src/Microsoft.AspNetCore.Server.IISIntegration.Tools/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..76feceeff0 --- /dev/null +++ b/src/Microsoft.AspNetCore.Server.IISIntegration.Tools/Properties/AssemblyInfo.cs @@ -0,0 +1,11 @@ +// 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.Reflection; +using System.Resources; + +[assembly: AssemblyMetadata("Serviceable", "True")] +[assembly: NeutralResourcesLanguage("en-us")] +[assembly: AssemblyCompany("Microsoft Corporation.")] +[assembly: AssemblyCopyright("© Microsoft Corporation. All rights reserved.")] +[assembly: AssemblyProduct("Microsoft ASP.NET Core")] diff --git a/src/Microsoft.AspNetCore.Server.IISIntegration.Tools/PublishIISCommand.cs b/src/Microsoft.AspNetCore.Server.IISIntegration.Tools/PublishIISCommand.cs new file mode 100644 index 0000000000..eea405c817 --- /dev/null +++ b/src/Microsoft.AspNetCore.Server.IISIntegration.Tools/PublishIISCommand.cs @@ -0,0 +1,119 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.IO; +using System.Xml; +using System.Xml.Linq; +using Microsoft.DotNet.Cli.Utils; +using Microsoft.DotNet.ProjectModel; +using Microsoft.Extensions.Configuration; + +namespace Microsoft.AspNetCore.Server.IISIntegration.Tools +{ + public class PublishIISCommand + { + private readonly string _publishFolder; + private readonly string _projectPath; + private readonly string _webRoot; + + public PublishIISCommand(string publishFolder, string projectPath, string webRoot) + { + _publishFolder = publishFolder; + _projectPath = projectPath; + _webRoot = webRoot; + } + + public int Run() + { + var applicationBasePath = GetApplicationBasePath(); + var webRoot = GetWebRoot(applicationBasePath); + + XDocument webConfigXml = null; + var webRootDirectory = Path.Combine(_publishFolder, webRoot); + var webConfigPath = Path.Combine(webRootDirectory, "web.config"); + if (File.Exists(webConfigPath)) + { + Reporter.Output.WriteLine($"Updating web.config at '{webConfigPath}'"); + + try + { + webConfigXml = XDocument.Load(webConfigPath); + } + catch (XmlException) { } + } + else + { + if (!Directory.Exists(webRootDirectory)) + { + Reporter.Output.WriteLine($"No webroot directory found. Creating '{webRootDirectory}'"); + Directory.CreateDirectory(webRootDirectory); + } + + Reporter.Output.WriteLine($"No web.config found. Creating '{webConfigPath}'"); + } + + var applicationName = GetApplicationName(applicationBasePath) + ".exe"; + var transformedConfig = WebConfigTransform.Transform(webConfigXml, applicationName, ConfigureForAzure()); + + using (var f = new FileStream(webConfigPath, FileMode.Create)) + { + transformedConfig.Save(f); + } + + return 0; + } + + private string GetApplicationBasePath() + { + if (!string.IsNullOrEmpty(_projectPath)) + { + var fullProjectPath = Path.GetFullPath(_projectPath); + + return Path.GetFileName(fullProjectPath) == "project.json" + ? Path.GetDirectoryName(fullProjectPath) + : fullProjectPath; + } + + return Directory.GetCurrentDirectory(); + } + + private string GetApplicationName(string applicationBasePath) + { + return ProjectReader.GetProject(Path.Combine(applicationBasePath, "project.json")).Name; + } + + private string GetWebRoot(string applicationBasePath) + { + if (!string.IsNullOrEmpty(_webRoot)) + { + return _webRoot; + } + + var builder = new ConfigurationBuilder() + .AddJsonFile(Path.Combine(applicationBasePath, "hosting.json"), optional: true); + + var webroot = builder.Build()["webroot"]; + + if (!string.IsNullOrEmpty(webroot)) + { + return webroot; + } + + if (Directory.Exists(Path.Combine(applicationBasePath, "wwwroot"))) + { + return "wwwroot"; + } + + return string.Empty; + } + + private static bool ConfigureForAzure() + { + var configureForAzureValue = Environment.GetEnvironmentVariable("DOTNET_CONFIGURE_AZURE"); + return string.Equals(configureForAzureValue, "true", StringComparison.Ordinal) || + string.Equals(configureForAzureValue, "1", StringComparison.Ordinal) || + !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("WEBSITE_SITE_NAME")); + } + } +} diff --git a/src/Microsoft.AspNetCore.Server.IISIntegration.Tools/WebConfigTransform.cs b/src/Microsoft.AspNetCore.Server.IISIntegration.Tools/WebConfigTransform.cs new file mode 100644 index 0000000000..2d6a0994f3 --- /dev/null +++ b/src/Microsoft.AspNetCore.Server.IISIntegration.Tools/WebConfigTransform.cs @@ -0,0 +1,104 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.IO; +using System.Linq; +using System.Xml.Linq; + +namespace Microsoft.AspNetCore.Server.IISIntegration.Tools +{ + public static class WebConfigTransform + { + public static XDocument Transform(XDocument webConfig, string appName, bool configureForAzure) + { + const string HandlersElementName = "handlers"; + const string httpPlatformElementName = "httpPlatform"; + + webConfig = webConfig == null || webConfig.Root.Name.LocalName != "configuration" + ? XDocument.Parse("") + : webConfig; + + var webServerSection = GetOrCreateChild(webConfig.Root, "system.webServer"); + + TransformHandlers(GetOrCreateChild(webServerSection, HandlersElementName)); + TransformHttpPlatform(GetOrCreateChild(webServerSection, httpPlatformElementName), appName, configureForAzure); + + // make sure that the httpPlatform element is after handlers element + var httpPlatformElement = webServerSection.Element(HandlersElementName) + .ElementsBeforeSelf(httpPlatformElementName).SingleOrDefault(); + if (httpPlatformElement != null) + { + httpPlatformElement.Remove(); + webServerSection.Element(HandlersElementName).AddAfterSelf(httpPlatformElement); + } + + return webConfig; + } + + private static void TransformHandlers(XElement handlersElement) + { + var platformHandlerElement = + handlersElement.Elements("add") + .FirstOrDefault(e => string.Equals((string)e.Attribute("name"), "httpplatformhandler", StringComparison.OrdinalIgnoreCase)); + + if (platformHandlerElement == null) + { + platformHandlerElement = new XElement("add"); + handlersElement.Add(platformHandlerElement); + } + + platformHandlerElement.SetAttributeValue("name", "httpPlatformHandler"); + SetAttributeValueIfEmpty(platformHandlerElement, "path", "*"); + SetAttributeValueIfEmpty(platformHandlerElement, "verb", "*"); + SetAttributeValueIfEmpty(platformHandlerElement, "modules", "httpPlatformHandler"); + SetAttributeValueIfEmpty(platformHandlerElement, "resourceType", "Unspecified"); + } + + private static void TransformHttpPlatform(XElement httpPlatformElement, string appName, bool configureForAzure) + { + var appPath = Path.Combine(configureForAzure ? @"%home%\site" : "..", appName); + var logPath = Path.Combine(configureForAzure ? @"\\?\%home%\LogFiles" : @"..\logs", "stdout.log"); + + httpPlatformElement.SetAttributeValue("processPath", appPath); + SetAttributeValueIfEmpty(httpPlatformElement, "stdoutLogEnabled", "false"); + SetAttributeValueIfEmpty(httpPlatformElement, "stdoutLogFile", logPath); + SetAttributeValueIfEmpty(httpPlatformElement, "startupTimeLimit", "3600"); + + AddApplicationBase(httpPlatformElement); + } + + private static void AddApplicationBase(XElement httpPlatformElement) + { + const string appBaseKeyName = "ASPNET_APPLICATIONBASE"; + + var envVariables = GetOrCreateChild(httpPlatformElement, "environmentVariables"); + var appBaseElement = envVariables.Elements("environmentVariable").SingleOrDefault(e => + string.Equals((string)e.Attribute("name"), appBaseKeyName, StringComparison.CurrentCultureIgnoreCase)); + + if (appBaseElement == null) + { + appBaseElement = new XElement("environmentVariable", new XAttribute("name", appBaseKeyName)); + envVariables.AddFirst(appBaseElement); + } + + appBaseElement.SetAttributeValue("value", "."); + } + + private static XElement GetOrCreateChild(XElement parent, string childName) + { + var childElement = parent.Element(childName); + if (childElement == null) + { + childElement = new XElement(childName); + parent.Add(childElement); + } + return childElement; + } + + private static void SetAttributeValueIfEmpty(XElement element, string attributeName, string value) + { + element.SetAttributeValue(attributeName, (string)element.Attribute(attributeName) ?? value); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Server.IISIntegration.Tools/project.json b/src/Microsoft.AspNetCore.Server.IISIntegration.Tools/project.json new file mode 100644 index 0000000000..130896ffb0 --- /dev/null +++ b/src/Microsoft.AspNetCore.Server.IISIntegration.Tools/project.json @@ -0,0 +1,23 @@ +{ + "version": "1.0.0-*", + "compilationOptions": { + "emitEntryPoint": true, + "warningsAsErrors": true, + "keyFile": "../../tools/Key.snk", + "nowarn": [ "CS1591" ], + "xmlDoc": true + }, + + "dependencies": { + "NETStandard.Library": "1.0.0-*", + "Microsoft.Extensions.CommandLineUtils": "1.0.0-*", + "Microsoft.Extensions.Configuration.Json": "1.0.0-*", + "Microsoft.DotNet.ProjectModel": "1.0.0-*", + "Microsoft.DotNet.Cli.Utils": "1.0.0-*" + }, + + "frameworks": { + "dnxcore50": { + } + } +} diff --git a/test/Microsoft.AspNetCore.Server.IISIntegration.Tools.Tests/Microsoft.AspNetCore.Server.IISIntegration.Tools.Tests.xproj b/test/Microsoft.AspNetCore.Server.IISIntegration.Tools.Tests/Microsoft.AspNetCore.Server.IISIntegration.Tools.Tests.xproj new file mode 100644 index 0000000000..e3882f78d1 --- /dev/null +++ b/test/Microsoft.AspNetCore.Server.IISIntegration.Tools.Tests/Microsoft.AspNetCore.Server.IISIntegration.Tools.Tests.xproj @@ -0,0 +1,19 @@ + + + + 14.0.24720 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + d0fa003d-de4c-480e-b4a4-bd38691b36ad + Microsoft.AspNetCore.Server.IISIntegration.Tools.Tests + ..\..\artifacts\obj\$(MSBuildProjectName) + ..\..\artifacts\bin\$(MSBuildProjectName)\ + + + + 2.0 + + + \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Server.IISIntegration.Tools.Tests/PublishIISCommandFacts.cs b/test/Microsoft.AspNetCore.Server.IISIntegration.Tools.Tests/PublishIISCommandFacts.cs new file mode 100644 index 0000000000..5c00ae007d --- /dev/null +++ b/test/Microsoft.AspNetCore.Server.IISIntegration.Tools.Tests/PublishIISCommandFacts.cs @@ -0,0 +1,153 @@ +using Xunit; +using System.IO; +using System.Linq; +using System.Xml.Linq; +using System; + +namespace Microsoft.AspNetCore.Server.IISIntegration.Tools.Tests +{ + public class PublishIISCommandFacts + { + private class Folders + { + public string TestRoot; + public string PublishOutput; + public string ProjectPath; + } + + [Fact] + public void PublishIIS_uses_default_values_if_options_not_specified() + { + var webRoot = "wwwroot"; + var folders = CreateTestDir("{}", webRoot); + + new PublishIISCommand(folders.PublishOutput, folders.ProjectPath, null).Run(); + + var processPath = (string)GetPublishedWebConfig(folders.PublishOutput, webRoot) + .Descendants("httpPlatform").Attributes("processPath").Single(); + + Assert.Equal($@"..\projectDir.exe", processPath); + + Directory.Delete(folders.TestRoot, recursive: true); + } + + [Theory] + [InlineData("awesomeApp")] + [InlineData("awesome.App")] + public void PublishIIS_reads_application_name_from_project_json_if_exists(string projectName) + { + var webRoot = "wwwroot"; + var folders = CreateTestDir($@"{{ ""name"": ""{projectName}"" }}", webRoot); + + new PublishIISCommand(folders.PublishOutput, folders.ProjectPath, null).Run(); + + var processPath = (string)GetPublishedWebConfig(folders.PublishOutput, webRoot) + .Descendants("httpPlatform").Attributes("processPath").Single(); + + Assert.Equal($@"..\{projectName}.exe", processPath); + + Directory.Delete(folders.TestRoot, recursive: true); + } + + [Fact] + public void PublishIIS_uses_webroot_from_hosting_json() + { + var webRoot = "mywebroot"; + var folders = CreateTestDir("{}", webRoot); + File.WriteAllText(Path.Combine(folders.ProjectPath, "hosting.json"), $"{{ \"webroot\": \"{webRoot}\"}}"); + + new PublishIISCommand(folders.PublishOutput, folders.ProjectPath, null).Run(); + + var processPath = (string)GetPublishedWebConfig(folders.PublishOutput, webRoot) + .Descendants("httpPlatform").Attributes("processPath").Single(); + + Assert.Equal(@"..\projectDir.exe", processPath); + + Directory.Delete(folders.TestRoot, recursive: true); + } + + [Fact] + public void PublishIIS_webroot_switch_takes_precedence_over_hosting_json() + { + var webRoot = "mywebroot"; + var folders = CreateTestDir("{}", webRoot); + File.WriteAllText(Path.Combine(folders.ProjectPath, "hosting.json"), $"{{ \"webroot\": \"wwwroot\"}}"); + + new PublishIISCommand(folders.PublishOutput, folders.ProjectPath, webRoot).Run(); + + var processPath = (string)GetPublishedWebConfig(folders.PublishOutput, webRoot) + .Descendants("httpPlatform").Attributes("processPath").Single(); + + Assert.Equal(@"..\projectDir.exe", processPath); + + Directory.Delete(folders.TestRoot, recursive: true); + } + + [Theory] + [InlineData("projectDir")] + [InlineData("project.Dir")] + public void PublishIIS_accepts_path_to_project_json_as_project_path(string projectDir) + { + var webRoot = "wwwroot"; + var folders = CreateTestDir("{}", webRoot, projectDir); + + new PublishIISCommand(folders.PublishOutput, Path.Combine(folders.ProjectPath, "project.json"), null).Run(); + + var processPath = (string)GetPublishedWebConfig(folders.PublishOutput, webRoot) + .Descendants("httpPlatform").Attributes("processPath").Single(); + + Assert.Equal($@"..\{projectDir}.exe", processPath); + + Directory.Delete(folders.TestRoot, recursive: true); + } + + [Fact] + public void PublishIIS_modifies_existing_web_config() + { + var webRoot = "wwwroot"; + var folders = CreateTestDir("{}", webRoot); + + File.WriteAllText(Path.Combine(folders.PublishOutput, webRoot, "web.config"), +@" + + + + + + +"); + + new PublishIISCommand(folders.PublishOutput, Path.Combine(folders.ProjectPath, "project.json"), null).Run(); + + var httpPlatformElement = GetPublishedWebConfig(folders.PublishOutput, webRoot) + .Descendants("httpPlatform").Single(); + + Assert.Equal(@"..\projectDir.exe", (string)httpPlatformElement.Attribute("processPath")); + Assert.Equal(@"1234", (string)httpPlatformElement.Attribute("startupTimeLimit")); + + Directory.Delete(folders.TestRoot, recursive: true); + } + + private XDocument GetPublishedWebConfig(string publishOut, string webRoot) + { + return XDocument.Load(Path.Combine(publishOut, webRoot, "web.config")); + } + + private Folders CreateTestDir(string projectJson, string webRoot, string projectDir = "projectDir") + { + var testRoot = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + Directory.CreateDirectory(testRoot); + + var projectPath = Path.Combine(testRoot, projectDir); + Directory.CreateDirectory(projectPath); + Directory.CreateDirectory(Path.Combine(projectPath, webRoot)); + File.WriteAllText(Path.Combine(projectPath, "project.json"), projectJson); + + var publishOut = Path.Combine(testRoot, "publishOut"); + Directory.CreateDirectory(publishOut); + Directory.CreateDirectory(Path.Combine(publishOut, webRoot)); + + return new Folders { TestRoot = testRoot, ProjectPath = projectPath, PublishOutput = publishOut }; + } + } +} diff --git a/test/Microsoft.AspNetCore.Server.IISIntegration.Tools.Tests/WebConfigTransformFacts.cs b/test/Microsoft.AspNetCore.Server.IISIntegration.Tools.Tests/WebConfigTransformFacts.cs new file mode 100644 index 0000000000..8c5cdee79c --- /dev/null +++ b/test/Microsoft.AspNetCore.Server.IISIntegration.Tools.Tests/WebConfigTransformFacts.cs @@ -0,0 +1,202 @@ +using Xunit; +using System.Linq; +using System.Xml.Linq; + +namespace Microsoft.AspNetCore.Server.IISIntegration.Tools.Tests +{ + public class WebConfigTransformFacts + { + private XDocument WebConfigTemplate => XDocument.Parse( +@" + + + + + + + + + + +"); + + [Fact] + public void WebConfigTransform_creates_new_config_if_one_does_not_exist() + { + Assert.True(XNode.DeepEquals(WebConfigTemplate, + WebConfigTransform.Transform(null, "test.exe", configureForAzure: false))); + } + + [Fact] + public void WebConfigTransform_creates_new_config_if_one_has_unexpected_format() + { + Assert.True(XNode.DeepEquals(WebConfigTemplate, + WebConfigTransform.Transform(XDocument.Parse(""), "test.exe", configureForAzure: false))); + } + + [Theory] + [InlineData(new object[] { new[] { "system.webServer" } })] + [InlineData(new object[] { new[] { "add" } })] + [InlineData(new object[] { new[] { "handlers" } })] + [InlineData(new object[] { new[] { "httpPlatform" } })] + [InlineData(new object[] { new[] { "environmentVariables" } })] + [InlineData(new object[] { new[] { "environmentVariable" } })] + [InlineData(new object[] { new[] { "handlers", "httpPlatform", "environmentVariables" } })] + public void WebConfigTransform_adds_missing_elements(string[] elementNames) + { + var input = WebConfigTemplate; + foreach (var elementName in elementNames) + { + input.Descendants(elementName).Remove(); + } + + Assert.True(XNode.DeepEquals(WebConfigTemplate, + WebConfigTransform.Transform(input, "test.exe", configureForAzure: false))); + } + + [Theory] + [InlineData("add", "path", "test")] + [InlineData("add", "verb", "test")] + [InlineData("add", "modules", "mods")] + [InlineData("add", "resourceType", "Either")] + [InlineData("httpPlatform", "stdoutLogEnabled", "true")] + [InlineData("httpPlatform", "startupTimeLimit", "1200")] + [InlineData("httpPlatform", "arguments", "arg1")] + [InlineData("httpPlatform", "stdoutLogFile", "logfile.log")] + public void WebConfigTransform_wont_override_custom_values(string elementName, string attributeName, string attributeValue) + { + var input = WebConfigTemplate; + input.Descendants(elementName).Single().SetAttributeValue(attributeName, attributeValue); + + var output = WebConfigTransform.Transform(input, "test.exe", configureForAzure: false); + Assert.Equal(attributeValue, (string)output.Descendants(elementName).Single().Attribute(attributeName)); + } + + [Fact] + public void WebConfigTransform_overwrites_processPath() + { + var newProcessPath = + (string)WebConfigTransform.Transform(WebConfigTemplate, "app.exe", configureForAzure: false) + .Descendants("httpPlatform").Single().Attribute("processPath"); + + Assert.Equal(@"..\app.exe", newProcessPath); + } + + [Fact] + public void WebConfigTransform_fixes_httpPlatformHandler_casing() + { + var input = WebConfigTemplate; + input.Descendants("add").Single().SetAttributeValue("name", "httpplatformhandler"); + + Assert.True(XNode.DeepEquals(WebConfigTemplate, + WebConfigTransform.Transform(input, "test.exe", configureForAzure: false))); + } + + [Fact] + public void WebConfigTransform_does_not_remove_children_of_httpPlatform_element() + { + var envVarElement = + new XElement("environmentVariable", new XAttribute("name", "ENVVAR"), new XAttribute("value", "123")); + + var input = WebConfigTemplate; + input.Descendants("environmentVariable").Single().Add(envVarElement); + + Assert.True(XNode.DeepEquals(envVarElement, + WebConfigTransform.Transform(input, "app.exe", configureForAzure: false) + .Descendants("environmentVariable").SingleOrDefault(e => (string)e.Attribute("name") == "ENVVAR"))); + } + + [Fact] + public void WebConfigTransform_adds_stdoutLogEnabled_if_attribute_is_missing() + { + var input = WebConfigTemplate; + input.Descendants("httpPlatform").Attributes("stdoutLogEnabled").Remove(); + + Assert.Equal( + "false", + (string)WebConfigTransform.Transform(input, "test.exe", configureForAzure: false) + .Descendants().Attributes("stdoutLogEnabled").Single()); + } + + [Theory] + [InlineData(null)] + [InlineData("false")] + [InlineData("true")] + public void WebConfigTransform_adds_stdoutLogFile_if_attribute_is_missing(string stdoutLogFile) + { + var input = WebConfigTemplate; + + var httpPlatformElement = input.Descendants("httpPlatform").Single(); + httpPlatformElement.Attribute("stdoutLogEnabled").Remove(); + if (stdoutLogFile != null) + { + httpPlatformElement.SetAttributeValue("stdoutLogEnabled", stdoutLogFile); + } + + Assert.Equal( + @"..\logs\stdout.log", + (string)WebConfigTransform.Transform(input, "test.exe", configureForAzure: false) + .Descendants().Attributes("stdoutLogFile").Single()); + } + + [Theory] + [InlineData(null)] + [InlineData("true")] + [InlineData("false")] + public void WebConfigTransform_does_not_change_existing_stdoutLogEnabled(string stdoutLogEnabledValue) + { + var input = WebConfigTemplate; + var httpPlatformElement = input.Descendants("httpPlatform").Single(); + + httpPlatformElement.SetAttributeValue("stdoutLogFile", "mylog.txt"); + httpPlatformElement.Attributes("stdoutLogEnabled").Remove(); + if (stdoutLogEnabledValue != null) + { + input.Descendants("httpPlatform").Single().SetAttributeValue("stdoutLogEnabled", stdoutLogEnabledValue); + } + + Assert.Equal( + "mylog.txt", + (string)WebConfigTransform.Transform(input, "test.exe", configureForAzure: false) + .Descendants().Attributes("stdoutLogFile").Single()); + } + + [Fact] + public void WebConfigTransform_correctly_configures_for_Azure() + { + var input = WebConfigTemplate; + input.Descendants("httpPlatform").Attributes().Remove(); + + var httPlatformElement = WebConfigTransform.Transform(input, "test.exe", configureForAzure: true) + .Descendants("httpPlatform").Single(); + httPlatformElement.Elements().Remove(); + + Assert.True(XNode.DeepEquals( + XDocument.Parse(@"").Root, + httPlatformElement)); + } + + [Fact] + public void WebConfigTransform_overrites_value_for_ASPNET_APPLICATIONBASE() + { + var input = WebConfigTemplate; + input.Descendants("environmentVariable").Single().SetAttributeValue("value", "abc"); + + Assert.True(XNode.DeepEquals(WebConfigTemplate, + WebConfigTransform.Transform(input, "test.exe", configureForAzure: false))); + } + + private bool VerifyMissingElementCreated(params string[] elementNames) + { + var input = WebConfigTemplate; + foreach (var elementName in elementNames) + { + input.Descendants(elementName).Remove(); + } + + return XNode.DeepEquals(WebConfigTemplate, + WebConfigTransform.Transform(input, "test.exe", configureForAzure: false)); + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Server.IISIntegration.Tools.Tests/project.json b/test/Microsoft.AspNetCore.Server.IISIntegration.Tools.Tests/project.json new file mode 100644 index 0000000000..4a3f14c43f --- /dev/null +++ b/test/Microsoft.AspNetCore.Server.IISIntegration.Tools.Tests/project.json @@ -0,0 +1,16 @@ +{ + "dependencies": { + "xunit": "2.1.0", + "dotnet-publish-iis": "1.0.0-*", + "Microsoft.NETCore.Platforms": "1.0.1-*" + }, + "frameworks": { + "dnxcore50": { + "imports": "portable-net451+win8", + "dependencies": { + "dotnet-test-xunit": "1.0.0-dev-*" + } + } + }, + "testRunner": "xunit" +}