diff --git a/TestApps.sln b/TestApps.sln index cc683f4..9a5a6ce 100644 --- a/TestApps.sln +++ b/TestApps.sln @@ -17,6 +17,8 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "ClassLibraryTagHelper", "te EndProject Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "ApplicationWithConfigureMvc", "testapps\ApplicationWithConfigureMvc\ApplicationWithConfigureMvc.xproj", "{E2EAEB85-91D5-478E-9CE2-964F68DE20D0}" EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "ClassLibraryWithPrecompiledViews", "testapps\ClassLibraryWithPrecompiledViews\ClassLibraryWithPrecompiledViews.xproj", "{4684DE8B-3FBE-421B-8798-56C3D6698B76}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -39,6 +41,10 @@ Global {E2EAEB85-91D5-478E-9CE2-964F68DE20D0}.Debug|Any CPU.Build.0 = Debug|Any CPU {E2EAEB85-91D5-478E-9CE2-964F68DE20D0}.Release|Any CPU.ActiveCfg = Release|Any CPU {E2EAEB85-91D5-478E-9CE2-964F68DE20D0}.Release|Any CPU.Build.0 = Release|Any CPU + {4684DE8B-3FBE-421B-8798-56C3D6698B76}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4684DE8B-3FBE-421B-8798-56C3D6698B76}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4684DE8B-3FBE-421B-8798-56C3D6698B76}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4684DE8B-3FBE-421B-8798-56C3D6698B76}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools/Internal/PrecompileDispatchCommand.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools/Internal/PrecompileDispatchCommand.cs index 7a95538..a784cb9 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools/Internal/PrecompileDispatchCommand.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools/Internal/PrecompileDispatchCommand.cs @@ -101,9 +101,9 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Precompilation.Internal #if DEBUG var commandLineArgs = Environment.GetCommandLineArgs(); - if (commandLineArgs.Length > 0 && commandLineArgs[0] == "--debug") + if (commandLineArgs.Length > 1 && commandLineArgs[1] == "--debug") { - dispatchArgs.Insert(0, commandLineArgs[0]); + dispatchArgs.Insert(0, commandLineArgs[1]); } #endif diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tests/ApplicationConsumingPrecompiledViews.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tests/ApplicationConsumingPrecompiledViews.cs new file mode 100644 index 0000000..fe441c7 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tests/ApplicationConsumingPrecompiledViews.cs @@ -0,0 +1,118 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.IO; +using System.Net.Http; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Server.IntegrationTesting; +using Microsoft.DotNet.Cli.Utils; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.Razor.Precompilation +{ + public class ApplicationConsumingPrecompiledViews + : IClassFixture + { + public ApplicationConsumingPrecompiledViews(ApplicationConsumingPrecompiledViewsFixture fixture) + { + Fixture = fixture; + } + + public ApplicationTestFixture Fixture { get; } + + [Theory] + [InlineData(RuntimeFlavor.Clr)] + [InlineData(RuntimeFlavor.CoreClr)] + public async Task ConsumingClassLibrariesWithPrecompiledViewsWork(RuntimeFlavor flavor) + { + // Arrange + using (var deployer = Fixture.CreateDeployment(flavor)) + { + var deploymentResult = deployer.Deploy(); + var httpClient = new HttpClient() + { + BaseAddress = new Uri(deploymentResult.ApplicationBaseUri) + }; + + // Act + var response = await httpClient.GetStringWithRetryAsync("Manage/Home", Fixture.Logger); + + // Assert + TestEmbeddedResource.AssertContent("ApplicationConsumingPrecompiledViews.Manage.Home.Index.txt", response); + } + } + + public class ApplicationConsumingPrecompiledViewsFixture : ApplicationTestFixture + { + private readonly string _packOutputDirectory = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + + public ApplicationConsumingPrecompiledViewsFixture() + : base("ApplicationUsingPrecompiledViewClassLibrary") + { + ClassLibraryPath = Path.GetFullPath(Path.Combine(ApplicationPath, "..", "ClassLibraryWithPrecompiledViews")); + } + + private string ClassLibraryPath { get; } + + protected override void Restore() + { + CreateClassLibraryPackage(); + RestoreProject(ApplicationPath, new[] { _packOutputDirectory }); + } + + private void CreateClassLibraryPackage() + { + RestoreProject(ClassLibraryPath); + ExecuteForClassLibrary(Command.CreateDotNet("build", new[] { ClassLibraryPath, "-c", "Release" })); + ExecuteForClassLibrary(Command.CreateDotNet( + "razor-precompile", + GetPrecompileArguments("net451"))); + + ExecuteForClassLibrary(Command.CreateDotNet( + "razor-precompile", + GetPrecompileArguments("netcoreapp1.0"))); + + var timestamp = "z" + DateTime.UtcNow.Ticks.ToString().PadLeft(18, '0'); + var packCommand = Command + .CreateDotNet("pack", new[] { "--no-build", "-c", "Release", "-o", _packOutputDirectory }) + .EnvironmentVariable("DOTNET_BUILD_VERSION", timestamp); + + ExecuteForClassLibrary(packCommand); + } + + private void ExecuteForClassLibrary(ICommand command) + { + Console.WriteLine($"Running {command.CommandName} {command.CommandArgs}"); + command + .WorkingDirectory(ClassLibraryPath) + .EnvironmentVariable(NuGetPackagesEnvironmentKey, TempRestoreDirectory) + .EnvironmentVariable(DotnetSkipFirstTimeExperience, "true") + .ForwardStdErr(Console.Error) + .ForwardStdOut(Console.Out) + .Execute() + .EnsureSuccessful(); + } + + private string[] GetPrecompileArguments(string targetFramework) + { + return new[] + { + ClassLibraryPath, + "-c", + "Release", + "-f", + $"{targetFramework}", + "-o", + $"obj/precompiled/{targetFramework}", + }; + } + + public override void Dispose() + { + TryDeleteDirectory(_packOutputDirectory); + base.Dispose(); + } + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tests/Infrastructure/ApplicationTestFixture.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tests/Infrastructure/ApplicationTestFixture.cs index 91e2881..11b19c6 100644 --- a/test/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tests/Infrastructure/ApplicationTestFixture.cs +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tests/Infrastructure/ApplicationTestFixture.cs @@ -5,17 +5,18 @@ using System; using System.Collections.Generic; using System.IO; using System.Runtime.InteropServices; -using System.Threading.Tasks; using Microsoft.AspNetCore.Server.IntegrationTesting; using Microsoft.DotNet.Cli.Utils; using Microsoft.Extensions.Logging; -using Xunit; namespace Microsoft.AspNetCore.Mvc.Razor.Precompilation { public abstract class ApplicationTestFixture : IDisposable { public const string NuGetPackagesEnvironmentKey = "NUGET_PACKAGES"; + public const string DotnetSkipFirstTimeExperience = "DOTNET_SKIP_FIRST_TIME_EXPERIENCE"; + public const string DotnetCLITelemetryOptOut = "DOTNET_CLI_TELEMETRY_OPTOUT"; + private readonly string _oldRestoreDirectory; private bool _isRestored; @@ -49,6 +50,14 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Precompilation NuGetPackagesEnvironmentKey, TempRestoreDirectory); + var skipFirstTimeCacheCreation = new KeyValuePair( + DotnetSkipFirstTimeExperience, + "true"); + + var telemetryOptOut = new KeyValuePair( + DotnetCLITelemetryOptOut, + "1"); + var deploymentParameters = new DeploymentParameters( ApplicationPath, ServerType.Kestrel, @@ -57,14 +66,19 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Precompilation { PublishApplicationBeforeDeployment = true, TargetFramework = flavor == RuntimeFlavor.Clr ? "net451" : "netcoreapp1.0", + PreservePublishedApplicationForDebugging = true, Configuration = "Release", EnvironmentVariables = { - tempRestoreDirectoryEnvironment + tempRestoreDirectoryEnvironment, + skipFirstTimeCacheCreation, + telemetryOptOut, }, PublishEnvironmentVariables = { - tempRestoreDirectoryEnvironment + tempRestoreDirectoryEnvironment, + skipFirstTimeCacheCreation, + telemetryOptOut, }, }; @@ -76,11 +90,16 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Precompilation RestoreProject(ApplicationPath); } - public void Dispose() + public virtual void Dispose() + { + TryDeleteDirectory(TempRestoreDirectory); + } + + protected static void TryDeleteDirectory(string directory) { try { - Directory.Delete(TempRestoreDirectory, recursive: true); + Directory.Delete(directory, recursive: true); } catch (IOException) { @@ -88,29 +107,32 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Precompilation } } - protected void RestoreProject(string applicationDirectory) + protected void RestoreProject(string applicationDirectory, string[] additionalFeeds = null) { var packagesDirectory = GetNuGetPackagesDirectory(); - var args = new[] + var args = new List { Path.Combine(applicationDirectory, "project.json"), "--packages", TempRestoreDirectory, }; - var commandResult = Command + if (additionalFeeds != null) + { + foreach (var feed in additionalFeeds) + { + args.Add("-f"); + args.Add(feed); + } + } + + Command .CreateDotNet("restore", args) + .EnvironmentVariable(DotnetSkipFirstTimeExperience, "true") .ForwardStdErr(Console.Error) .ForwardStdOut(Console.Out) - .Execute(); - - Assert.True(commandResult.ExitCode == 0, - string.Join(Environment.NewLine, - $"dotnet {commandResult.StartInfo.Arguments} exited with {commandResult.ExitCode}.", - commandResult.StdOut, - commandResult.StdErr)); - - Console.WriteLine(commandResult.StdOut); + .Execute() + .EnsureSuccessful(); } private static string CreateTempRestoreDirectory() diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tests/Infrastructure/CommandResultExtensions.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tests/Infrastructure/CommandResultExtensions.cs new file mode 100644 index 0000000..fae4b2a --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tests/Infrastructure/CommandResultExtensions.cs @@ -0,0 +1,25 @@ +// 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 Xunit; + +namespace Microsoft.AspNetCore.Mvc.Razor.Precompilation +{ + public static class CommandResultExtensions + { + public static CommandResult EnsureSuccessful(this CommandResult commandResult) + { + var startInfo = commandResult.StartInfo; + + Assert.True(commandResult.ExitCode == 0, + string.Join(Environment.NewLine, + $"{startInfo.FileName} {startInfo.Arguments} exited with {commandResult.ExitCode}.", + commandResult.StdOut, + commandResult.StdErr)); + + return commandResult; + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tests/Resources/ApplicationConsumingPrecompiledViews.Manage.Home.Index.txt b/test/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tests/Resources/ApplicationConsumingPrecompiledViews.Manage.Home.Index.txt new file mode 100644 index 0000000..de01ff4 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tests/Resources/ApplicationConsumingPrecompiledViews.Manage.Home.Index.txt @@ -0,0 +1,13 @@ + + + + AspNetCore.Areas_Manage_Views_Shared__Layout_cshtml, ClassLibraryWithPrecompiledViews.PrecompiledViews, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null + +

Admin home page

+ + + + Test section + + + diff --git a/testapps/ApplicationUsingPrecompiledViewClassLibrary/ApplicationUsingPrecompiledViewClassLibrary.xproj b/testapps/ApplicationUsingPrecompiledViewClassLibrary/ApplicationUsingPrecompiledViewClassLibrary.xproj new file mode 100644 index 0000000..0559856 --- /dev/null +++ b/testapps/ApplicationUsingPrecompiledViewClassLibrary/ApplicationUsingPrecompiledViewClassLibrary.xproj @@ -0,0 +1,19 @@ + + + + 14.0.25420 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + cef718e4-4556-422f-b64f-9d9671218e67 + ApplicationUsingPrecompiledViewClassLibrary + .\obj + .\bin\ + + + + 2.0 + + + \ No newline at end of file diff --git a/testapps/ApplicationUsingPrecompiledViewClassLibrary/Program.cs b/testapps/ApplicationUsingPrecompiledViewClassLibrary/Program.cs new file mode 100644 index 0000000..ea75f8c --- /dev/null +++ b/testapps/ApplicationUsingPrecompiledViewClassLibrary/Program.cs @@ -0,0 +1,26 @@ +using System.IO; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; + +namespace ApplicationWithTagHelpers +{ + public class Program + { + public static void Main(string[] args) + { + var config = new ConfigurationBuilder() + .AddCommandLine(args) + .AddEnvironmentVariables(prefix: "ASPNETCORE_") + .Build(); + + var host = new WebHostBuilder() + .UseConfiguration(config) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseStartup() + .Build(); + + host.Run(); + } + } +} diff --git a/testapps/ApplicationUsingPrecompiledViewClassLibrary/Startup.cs b/testapps/ApplicationUsingPrecompiledViewClassLibrary/Startup.cs new file mode 100644 index 0000000..85f98e4 --- /dev/null +++ b/testapps/ApplicationUsingPrecompiledViewClassLibrary/Startup.cs @@ -0,0 +1,29 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace ApplicationWithTagHelpers +{ + public class Startup + { + public void ConfigureServices(IServiceCollection services) + { + // Add framework services. + services.AddMvc(); + } + + public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory) + { + loggerFactory.AddConsole(); + app.UseMvc(routes => + { + routes.MapRoute(name: "areaRoute", + template: "{area:exists}/{controller=Home}/{action=Index}"); + + routes.MapRoute( + name: "default", + template: "{controller=Home}/{action=Index}/{id?}"); + }); + } + } +} diff --git a/testapps/ApplicationUsingPrecompiledViewClassLibrary/project.json b/testapps/ApplicationUsingPrecompiledViewClassLibrary/project.json new file mode 100644 index 0000000..4ecb593 --- /dev/null +++ b/testapps/ApplicationUsingPrecompiledViewClassLibrary/project.json @@ -0,0 +1,36 @@ +{ + "buildOptions": { + "emitEntryPoint": true, + "preserveCompilationContext": true + }, + "dependencies": { + "ClassLibraryWithPrecompiledViews": { + "target": "package", + "version": "1.0.0-*" + }, + "Microsoft.AspNetCore.Mvc": "1.1.0-*", + "Microsoft.AspNetCore.Server.Kestrel": "1.1.0-*", + "Microsoft.Extensions.Configuration.CommandLine": "1.1.0-*", + "Microsoft.Extensions.Logging.Console": "1.1.0-*" + }, + + "frameworks": { + "netcoreapp1.0": { + "dependencies": { + "Microsoft.NETCore.App": { + "version": "1.0.0", + "type": "platform" + } + } + }, + "net451": {} + }, + + "publishOptions": { + "include": [ + "wwwroot", + "appsettings.json", + "web.config" + ] + } +} diff --git a/testapps/ClassLibraryWithPrecompiledViews/Areas/Manage/HomeController.cs b/testapps/ClassLibraryWithPrecompiledViews/Areas/Manage/HomeController.cs new file mode 100644 index 0000000..7bb8681 --- /dev/null +++ b/testapps/ClassLibraryWithPrecompiledViews/Areas/Manage/HomeController.cs @@ -0,0 +1,10 @@ +using Microsoft.AspNetCore.Mvc; + +namespace ClassLibraryWithPrecompiledViews +{ + [Area("Manage")] + public class HomeController : Controller + { + public IActionResult Index() => View(); + } +} diff --git a/testapps/ClassLibraryWithPrecompiledViews/Areas/Manage/Views/Home/Index.cshtml b/testapps/ClassLibraryWithPrecompiledViews/Areas/Manage/Views/Home/Index.cshtml new file mode 100644 index 0000000..51a348b --- /dev/null +++ b/testapps/ClassLibraryWithPrecompiledViews/Areas/Manage/Views/Home/Index.cshtml @@ -0,0 +1,9 @@ +@{ + Layout = "_Layout"; +} + +

Admin home page

+ +@section TestSection { + Test section +} \ No newline at end of file diff --git a/testapps/ClassLibraryWithPrecompiledViews/Areas/Manage/Views/Shared/_Layout.cshtml b/testapps/ClassLibraryWithPrecompiledViews/Areas/Manage/Views/Shared/_Layout.cshtml new file mode 100644 index 0000000..6578e0e --- /dev/null +++ b/testapps/ClassLibraryWithPrecompiledViews/Areas/Manage/Views/Shared/_Layout.cshtml @@ -0,0 +1,8 @@ + + + + @GetType().AssemblyQualifiedName + @RenderBody() + @RenderSection("TestSection") + + diff --git a/testapps/ClassLibraryWithPrecompiledViews/ClassLibraryWithPrecompiledViews.xproj b/testapps/ClassLibraryWithPrecompiledViews/ClassLibraryWithPrecompiledViews.xproj new file mode 100644 index 0000000..217dde2 --- /dev/null +++ b/testapps/ClassLibraryWithPrecompiledViews/ClassLibraryWithPrecompiledViews.xproj @@ -0,0 +1,19 @@ + + + + 14.0.25420 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + 4684de8b-3fbe-421b-8798-56c3d6698b76 + ClassLibraryWithPrecompiledViews + .\obj + .\bin\ + + + + 2.0 + + + \ No newline at end of file diff --git a/testapps/ClassLibraryWithPrecompiledViews/Program.cs b/testapps/ClassLibraryWithPrecompiledViews/Program.cs new file mode 100644 index 0000000..50d50a8 --- /dev/null +++ b/testapps/ClassLibraryWithPrecompiledViews/Program.cs @@ -0,0 +1,9 @@ +namespace ClassLibraryWithPrecompiledViews +{ + public class Program + { + public static void Main() + { + } + } +} \ No newline at end of file diff --git a/testapps/ClassLibraryWithPrecompiledViews/project.json b/testapps/ClassLibraryWithPrecompiledViews/project.json new file mode 100644 index 0000000..edc9e94 --- /dev/null +++ b/testapps/ClassLibraryWithPrecompiledViews/project.json @@ -0,0 +1,37 @@ +{ + "version": "1.0.0-*", + "buildOptions": { + "preserveCompilationContext": true, + "emitEntryPoint": true + }, + "packOptions": { + "files": { + "mappings": { + "lib/": "obj/precompiled/" + } + } + }, + "dependencies": { + "Microsoft.AspNetCore.Mvc": "1.1.0-*", + "Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design": { + "version": "1.0.0-*", + "target": "package", + "type": "build" + } + }, + "tools": { + "Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools": "1.0.0-*" + }, + + "frameworks": { + "netcoreapp1.0": { + "dependencies": { + "Microsoft.NETCore.App": { + "version": "1.0.0", + "type": "platform" + } + } + }, + "net451": {} + } +}