diff --git a/src/Microsoft.AspNetCore.Server.IISIntegration.Tools/Program.cs b/src/Microsoft.AspNetCore.Server.IISIntegration.Tools/Program.cs index 8a341e5f18..e4312fd81d 100644 --- a/src/Microsoft.AspNetCore.Server.IISIntegration.Tools/Program.cs +++ b/src/Microsoft.AspNetCore.Server.IISIntegration.Tools/Program.cs @@ -20,13 +20,15 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.Tools app.HelpOption("-h|--help"); var publishFolderOption = app.Option("--publish-folder|-p", "The path to the publish output folder", CommandOptionType.SingleValue); + var frameworkOption = app.Option("-f|--framework ", "Target framework of application being published", 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(); + var framework = frameworkOption.Value(); - if (publishFolder == null) + if (publishFolder == null || framework == null) { app.ShowHelp(); return 2; @@ -34,7 +36,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.Tools Reporter.Output.WriteLine($"Configuring the following project for use with IIS: '{publishFolder}'"); - var exitCode = new PublishIISCommand(publishFolder, projectPath.Value).Run(); + var exitCode = new PublishIISCommand(publishFolder, framework, projectPath.Value).Run(); Reporter.Output.WriteLine("Configuring project completed successfully"); diff --git a/src/Microsoft.AspNetCore.Server.IISIntegration.Tools/PublishIISCommand.cs b/src/Microsoft.AspNetCore.Server.IISIntegration.Tools/PublishIISCommand.cs index 56db9baf76..4d776cf357 100644 --- a/src/Microsoft.AspNetCore.Server.IISIntegration.Tools/PublishIISCommand.cs +++ b/src/Microsoft.AspNetCore.Server.IISIntegration.Tools/PublishIISCommand.cs @@ -3,10 +3,12 @@ using System; using System.IO; +using System.Linq; using System.Xml; using System.Xml.Linq; using Microsoft.DotNet.ProjectModel; using Microsoft.Extensions.Cli.Utils; +using NuGet.Frameworks; namespace Microsoft.AspNetCore.Server.IISIntegration.Tools { @@ -14,11 +16,13 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.Tools { private readonly string _publishFolder; private readonly string _projectPath; + private readonly string _framework; - public PublishIISCommand(string publishFolder, string projectPath) + public PublishIISCommand(string publishFolder, string framework, string projectPath) { _publishFolder = publishFolder; _projectPath = projectPath; + _framework = framework; } public int Run() @@ -42,8 +46,10 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.Tools Reporter.Output.WriteLine($"No web.config found. Creating '{webConfigPath}'"); } - var applicationName = GetApplicationName(applicationBasePath) + ".exe"; - var transformedConfig = WebConfigTransform.Transform(webConfigXml, applicationName, ConfigureForAzure()); + var projectContext = GetProjectContext(applicationBasePath, _framework); + var isPortable = !projectContext.TargetFramework.IsDesktop() && projectContext.IsPortable; + var applicationName = projectContext.ProjectFile.Name + (isPortable ? ".dll" : ".exe"); + var transformedConfig = WebConfigTransform.Transform(webConfigXml, applicationName, ConfigureForAzure(), isPortable); using (var f = new FileStream(webConfigPath, FileMode.Create)) { @@ -67,9 +73,14 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.Tools return Directory.GetCurrentDirectory(); } - private string GetApplicationName(string applicationBasePath) + private static ProjectContext GetProjectContext(string applicationBasePath, string framework) { - return ProjectReader.GetProject(Path.Combine(applicationBasePath, "project.json")).Name; + var project = ProjectReader.GetProject(Path.Combine(applicationBasePath, "project.json")); + + return new ProjectContextBuilder() + .WithProject(project) + .WithTargetFramework(framework) + .Build(); } private static bool ConfigureForAzure() diff --git a/src/Microsoft.AspNetCore.Server.IISIntegration.Tools/WebConfigTransform.cs b/src/Microsoft.AspNetCore.Server.IISIntegration.Tools/WebConfigTransform.cs index 34d1fdc1fa..d5cfc66af2 100644 --- a/src/Microsoft.AspNetCore.Server.IISIntegration.Tools/WebConfigTransform.cs +++ b/src/Microsoft.AspNetCore.Server.IISIntegration.Tools/WebConfigTransform.cs @@ -10,7 +10,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.Tools { public static class WebConfigTransform { - public static XDocument Transform(XDocument webConfig, string appName, bool configureForAzure) + public static XDocument Transform(XDocument webConfig, string appName, bool configureForAzure, bool isPortable) { const string HandlersElementName = "handlers"; const string aspNetCoreElementName = "aspNetCore"; @@ -22,7 +22,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.Tools var webServerSection = GetOrCreateChild(webConfig.Root, "system.webServer"); TransformHandlers(GetOrCreateChild(webServerSection, HandlersElementName)); - TransformAspNetCore(GetOrCreateChild(webServerSection, aspNetCoreElementName), appName, configureForAzure); + TransformAspNetCore(GetOrCreateChild(webServerSection, aspNetCoreElementName), appName, configureForAzure, isPortable); // make sure that the aspNetCore element is after handlers element var aspNetCoreElement = webServerSection.Element(HandlersElementName) @@ -55,14 +55,32 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.Tools SetAttributeValueIfEmpty(aspNetCoreElement, "resourceType", "Unspecified"); } - private static void TransformAspNetCore(XElement aspNetCoreElement, string appName, bool configureForAzure) + private static void TransformAspNetCore(XElement aspNetCoreElement, string appName, bool configureForAzure, bool isPortable) { // Forward slashes currently work neither in AspNetCoreModule nor in dotnet so they need to be // replaced with backwards slashes when the application is published on a non-Windows machine var appPath = Path.Combine(configureForAzure ? @"%home%\site" : ".", appName).Replace("/", "\\"); var logPath = Path.Combine(configureForAzure ? @"\\?\%home%\LogFiles" : @".\logs", "stdout").Replace("/", "\\"); - aspNetCoreElement.SetAttributeValue("processPath", appPath); + if (!isPortable) + { + aspNetCoreElement.SetAttributeValue("processPath", appPath); + } + else + { + aspNetCoreElement.SetAttributeValue("processPath", "dotnet"); + + // In Xml the order of attributes does not matter but it is nice to have + // the `arguments` attribute next to the `processPath` attribute + aspNetCoreElement.Attribute("arguments")?.Remove(); + var attributes = aspNetCoreElement.Attributes().ToList(); + var processPathIndex = attributes.FindIndex(a => a.Name.LocalName == "processPath"); + attributes.Insert(processPathIndex + 1, new XAttribute("arguments", appPath)); + + aspNetCoreElement.Attributes().Remove(); + aspNetCoreElement.Add(attributes); + } + SetAttributeValueIfEmpty(aspNetCoreElement, "stdoutLogEnabled", "false"); SetAttributeValueIfEmpty(aspNetCoreElement, "stdoutLogFile", logPath); SetAttributeValueIfEmpty(aspNetCoreElement, "startupTimeLimit", "3600"); diff --git a/test/Microsoft.AspNetCore.Server.IISIntegration.Tools.Tests/PublishIISCommandFacts.cs b/test/Microsoft.AspNetCore.Server.IISIntegration.Tools.Tests/PublishIISCommandFacts.cs index 5e88a88aa8..3208870b50 100644 --- a/test/Microsoft.AspNetCore.Server.IISIntegration.Tools.Tests/PublishIISCommandFacts.cs +++ b/test/Microsoft.AspNetCore.Server.IISIntegration.Tools.Tests/PublishIISCommandFacts.cs @@ -15,17 +15,48 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.Tools.Tests public string ProjectPath; } - [Fact] - public void PublishIIS_uses_default_values_if_options_not_specified() + [Theory] + [InlineData("netcoreapp1.0")] + [InlineData("netstandard1.5")] + public void PublishIIS_uses_default_values_if_options_not_specified(string targetFramework) { - var folders = CreateTestDir("{}"); + var folders = CreateTestDir($@"{{ ""frameworks"": {{ ""{targetFramework}"": {{ }} }} }}"); - new PublishIISCommand(folders.PublishOutput, folders.ProjectPath).Run(); + new PublishIISCommand(folders.PublishOutput, targetFramework, folders.ProjectPath).Run(); var processPath = (string)GetPublishedWebConfig(folders.PublishOutput) .Descendants("aspNetCore").Attributes("processPath").Single(); - Assert.Equal($@".\projectDir.exe", processPath); + Assert.Equal(@".\projectDir.exe", processPath); + + Directory.Delete(folders.TestRoot, recursive: true); + } + + [Fact] + public void PublishIIS_can_publish_for_portable_app() + { + var folders = CreateTestDir( +@" + { + ""frameworks"": { + ""netcoreapp1.0"": { + ""dependencies"": { + ""Microsoft.NETCore.App"": { + ""version"": ""1.0.0-*"", + ""type"": ""platform"" + } + } + } + } + }"); + + new PublishIISCommand(folders.PublishOutput, "netcoreapp1.0", folders.ProjectPath).Run(); + + var aspNetCoreElement = GetPublishedWebConfig(folders.PublishOutput) + .Descendants("aspNetCore").Single(); + + Assert.Equal(@"dotnet", (string)aspNetCoreElement.Attribute("processPath")); + Assert.Equal(@".\projectDir.dll", (string)aspNetCoreElement.Attribute("arguments")); Directory.Delete(folders.TestRoot, recursive: true); } @@ -35,9 +66,9 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.Tools.Tests [InlineData("awesome.App")] public void PublishIIS_reads_application_name_from_project_json_if_exists(string projectName) { - var folders = CreateTestDir($@"{{ ""name"": ""{projectName}"" }}"); + var folders = CreateTestDir($@"{{ ""name"": ""{projectName}"", ""frameworks"": {{ ""netcoreapp1.0"": {{}} }} }}"); - new PublishIISCommand(folders.PublishOutput, folders.ProjectPath).Run(); + new PublishIISCommand(folders.PublishOutput, "netcoreapp1.0", folders.ProjectPath).Run(); var processPath = (string)GetPublishedWebConfig(folders.PublishOutput) .Descendants("aspNetCore").Attributes("processPath").Single(); @@ -52,9 +83,10 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.Tools.Tests [InlineData("project.Dir")] public void PublishIIS_accepts_path_to_project_json_as_project_path(string projectDir) { - var folders = CreateTestDir("{}", projectDir); + var folders = CreateTestDir(@"{ ""frameworks"": { ""netcoreapp1.0"": { } } }", projectDir); - new PublishIISCommand(folders.PublishOutput, Path.Combine(folders.ProjectPath, "project.json")).Run(); + new PublishIISCommand(folders.PublishOutput, "netcoreapp1.0", + Path.Combine(folders.ProjectPath, "project.json")).Run(); var processPath = (string)GetPublishedWebConfig(folders.PublishOutput) .Descendants("aspNetCore").Attributes("processPath").Single(); @@ -67,7 +99,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.Tools.Tests [Fact] public void PublishIIS_modifies_existing_web_config() { - var folders = CreateTestDir("{}"); + var folders = CreateTestDir(@"{ ""frameworks"": { ""netcoreapp1.0"": { } } }"); File.WriteAllText(Path.Combine(folders.PublishOutput, "web.config"), @" @@ -79,7 +111,8 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.Tools.Tests "); - new PublishIISCommand(folders.PublishOutput, Path.Combine(folders.ProjectPath, "project.json")).Run(); + new PublishIISCommand(folders.PublishOutput, "netcoreapp1.0", + Path.Combine(folders.ProjectPath, "project.json")).Run(); var aspNetCoreElement = GetPublishedWebConfig(folders.PublishOutput) .Descendants("aspNetCore").Single(); diff --git a/test/Microsoft.AspNetCore.Server.IISIntegration.Tools.Tests/WebConfigTransformFacts.cs b/test/Microsoft.AspNetCore.Server.IISIntegration.Tools.Tests/WebConfigTransformFacts.cs index 0a86a7eef5..51720967b2 100644 --- a/test/Microsoft.AspNetCore.Server.IISIntegration.Tools.Tests/WebConfigTransformFacts.cs +++ b/test/Microsoft.AspNetCore.Server.IISIntegration.Tools.Tests/WebConfigTransformFacts.cs @@ -20,14 +20,14 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.Tools.Tests public void WebConfigTransform_creates_new_config_if_one_does_not_exist() { Assert.True(XNode.DeepEquals(WebConfigTemplate, - WebConfigTransform.Transform(null, "test.exe", configureForAzure: false))); + WebConfigTransform.Transform(null, "test.exe", configureForAzure: false, isPortable: 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))); + WebConfigTransform.Transform(XDocument.Parse(""), "test.exe", configureForAzure: false, isPortable: false))); } [Theory] @@ -47,7 +47,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.Tools.Tests } Assert.True(XNode.DeepEquals(WebConfigTemplate, - WebConfigTransform.Transform(input, "test.exe", configureForAzure: false))); + WebConfigTransform.Transform(input, "test.exe", configureForAzure: false, isPortable: false))); } [Theory] @@ -64,7 +64,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.Tools.Tests var input = WebConfigTemplate; input.Descendants(elementName).Single().SetAttributeValue(attributeName, attributeValue); - var output = WebConfigTransform.Transform(input, "test.exe", configureForAzure: false); + var output = WebConfigTransform.Transform(input, "test.exe", configureForAzure: false, isPortable: false); Assert.Equal(attributeValue, (string)output.Descendants(elementName).Single().Attribute(attributeName)); } @@ -72,7 +72,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.Tools.Tests public void WebConfigTransform_overwrites_processPath() { var newProcessPath = - (string)WebConfigTransform.Transform(WebConfigTemplate, "app.exe", configureForAzure: false) + (string)WebConfigTransform.Transform(WebConfigTemplate, "app.exe", configureForAzure: false, isPortable: false) .Descendants("aspNetCore").Single().Attribute("processPath"); Assert.Equal(@".\app.exe", newProcessPath); @@ -85,7 +85,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.Tools.Tests input.Descendants("add").Single().SetAttributeValue("name", "aspnetcore"); Assert.True(XNode.DeepEquals(WebConfigTemplate, - WebConfigTransform.Transform(input, "test.exe", configureForAzure: false))); + WebConfigTransform.Transform(input, "test.exe", configureForAzure: false, isPortable: false))); } [Fact] @@ -98,7 +98,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.Tools.Tests input.Descendants("aspNetCore").Single().Add(envVarElement); Assert.True(XNode.DeepEquals(envVarElement, - WebConfigTransform.Transform(input, "app.exe", configureForAzure: false) + WebConfigTransform.Transform(input, "app.exe", configureForAzure: false, isPortable: false) .Descendants("environmentVariable").SingleOrDefault(e => (string)e.Attribute("name") == "ENVVAR"))); } @@ -110,7 +110,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.Tools.Tests Assert.Equal( "false", - (string)WebConfigTransform.Transform(input, "test.exe", configureForAzure: false) + (string)WebConfigTransform.Transform(input, "test.exe", configureForAzure: false, isPortable: false) .Descendants().Attributes("stdoutLogEnabled").Single()); } @@ -131,7 +131,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.Tools.Tests Assert.Equal( @".\logs\stdout", - (string)WebConfigTransform.Transform(input, "test.exe", configureForAzure: false) + (string)WebConfigTransform.Transform(input, "test.exe", configureForAzure: false, isPortable: false) .Descendants().Attributes("stdoutLogFile").Single()); } @@ -153,7 +153,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.Tools.Tests Assert.Equal( "mylog.txt", - (string)WebConfigTransform.Transform(input, "test.exe", configureForAzure: false) + (string)WebConfigTransform.Transform(input, "test.exe", configureForAzure: false, isPortable: false) .Descendants().Attributes("stdoutLogFile").Single()); } @@ -163,7 +163,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.Tools.Tests var input = WebConfigTemplate; input.Descendants("aspNetCore").Attributes().Remove(); - var aspNetCoreElement = WebConfigTransform.Transform(input, "test.exe", configureForAzure: true) + var aspNetCoreElement = WebConfigTransform.Transform(input, "test.exe", configureForAzure: true, isPortable: false) .Descendants("aspNetCore").Single(); aspNetCoreElement.Elements().Remove(); @@ -173,6 +173,35 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.Tools.Tests aspNetCoreElement)); } + [Fact] + public void WebConfigTransform_configures_portable_apps_correctly() + { + var aspNetCoreElement = + WebConfigTransform.Transform(WebConfigTemplate, "test.exe", configureForAzure: false, isPortable: true) + .Descendants("aspNetCore").Single(); + + Assert.True(XNode.DeepEquals( + XDocument.Parse(@"").Root, + aspNetCoreElement)); + } + + [Fact] + public void WebConfigTransform_overwrites_existing_arguments_attribute_for_portable_apps() + { + var input = WebConfigTemplate; + input.Descendants("aspNetCore").Single().SetAttributeValue("arguments", "42"); + + var aspNetCoreElement = + WebConfigTransform.Transform(input, "test.exe", configureForAzure: false, isPortable: true) + .Descendants("aspNetCore").Single(); + + Assert.True(XNode.DeepEquals( + XDocument.Parse(@"").Root, + aspNetCoreElement)); + } + private bool VerifyMissingElementCreated(params string[] elementNames) { var input = WebConfigTemplate; @@ -182,7 +211,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.Tools.Tests } return XNode.DeepEquals(WebConfigTemplate, - WebConfigTransform.Transform(input, "test.exe", configureForAzure: false)); + WebConfigTransform.Transform(input, "test.exe", configureForAzure: false, isPortable: false)); } } } \ No newline at end of file