diff --git a/TestApps.sln b/TestApps.sln index 9a5a6ce8ab..9cfeb8f1b1 100644 --- a/TestApps.sln +++ b/TestApps.sln @@ -19,6 +19,8 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "ApplicationWithConfigureMvc EndProject Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "ClassLibraryWithPrecompiledViews", "testapps\ClassLibraryWithPrecompiledViews\ClassLibraryWithPrecompiledViews.xproj", "{4684DE8B-3FBE-421B-8798-56C3D6698B76}" EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "PublishWIthEmbedViewSources", "testapps\PublishWIthEmbedViewSources\PublishWIthEmbedViewSources.xproj", "{E3462190-3068-40F0-9AA5-34779FE252AC}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -45,6 +47,10 @@ Global {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 + {E3462190-3068-40F0-9AA5-34779FE252AC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E3462190-3068-40F0-9AA5-34779FE252AC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E3462190-3068-40F0-9AA5-34779FE252AC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E3462190-3068-40F0-9AA5-34779FE252AC}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/CommonOptions.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/CommonOptions.cs index 60a26335ec..71d8e7b3f6 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/CommonOptions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/CommonOptions.cs @@ -9,6 +9,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design.Internal { public static readonly string ConfigureCompilationTypeTemplate = "--configure-compilation-type"; public static readonly string ContentRootTemplate = "--content-root"; + public static readonly string EmbedViewSourceTemplate = "--embed-view-sources"; public CommandArgument ProjectArgument { get; private set; } @@ -16,6 +17,8 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design.Internal public CommandOption ContentRootOption { get; private set; } + public CommandOption EmbedViewSourcesOption { get; private set; } + public void Configure(CommandLineApplication app) { ProjectArgument = app.Argument( @@ -31,6 +34,11 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design.Internal ContentRootTemplate, "The application's content root.", CommandOptionType.SingleValue); + + EmbedViewSourcesOption = app.Option( + EmbedViewSourceTemplate, + "Embed view sources as resources in the generated assembly.", + CommandOptionType.NoValue); } } } diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/PrecompileRunCommand.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/PrecompileRunCommand.cs index 1e599b1a81..f223a0f6d4 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/PrecompileRunCommand.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/PrecompileRunCommand.cs @@ -95,9 +95,10 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design.Internal var precompileAssemblyName = $"{ApplicationNameOption.Value()}{AssemblyPart.PrecompiledViewsAssemblySuffix}"; var compilation = CompileViews(results, precompileAssemblyName); + var resources = GetResources(results); var assemblyPath = Path.Combine(OutputPathOption.Value(), precompileAssemblyName + ".dll"); - var emitResult = EmitAssembly(compilation, assemblyPath); + var emitResult = EmitAssembly(compilation, assemblyPath, resources); if (!emitResult.Success) { @@ -115,7 +116,31 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design.Internal return 0; } - private EmitResult EmitAssembly(CSharpCompilation compilation, string assemblyPath) + private ResourceDescription[] GetResources(ViewCompilationInfo[] results) + { + if (!Options.EmbedViewSourcesOption.HasValue()) + { + return new ResourceDescription[0]; + } + + var resources = new ResourceDescription[results.Length]; + for (var i = 0; i < results.Length; i++) + { + var fileInfo = results[i].RelativeFileInfo; + + resources[i] = new ResourceDescription( + fileInfo.RelativePath.Replace('\\', '/'), + fileInfo.FileInfo.CreateReadStream, + isPublic: true); + } + + return resources; + } + + private EmitResult EmitAssembly( + CSharpCompilation compilation, + string assemblyPath, + ResourceDescription[] resources) { Directory.CreateDirectory(Path.GetDirectoryName(assemblyPath)); @@ -127,6 +152,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design.Internal emitResult = compilation.Emit( assemblyStream, pdbStream, + manifestResources: resources, options: MvcServiceProvider.Compiler.EmitOptions); } } 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 a784cb9231..c8bb5185cf 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools/Internal/PrecompileDispatchCommand.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools/Internal/PrecompileDispatchCommand.cs @@ -29,6 +29,8 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Precompilation.Internal private CommandOption BuildBasePathOption { get; set; } + private CommandOption DumpFilesOption { get; set; } + private string ProjectPath { get; set; } private string Configuration { get; set; } @@ -81,6 +83,11 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Precompilation.Internal dispatchArgs.Add(Options.ConfigureCompilationType.Value()); } + if (Options.EmbedViewSourcesOption.HasValue()) + { + dispatchArgs.Add(CommonOptions.EmbedViewSourceTemplate); + } + var compilerOptions = runtimeContext.ProjectFile.GetCompilerOptions(TargetFramework, Configuration); if (!string.IsNullOrEmpty(compilerOptions.KeyFile)) { diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Precompilation.FunctionalTests/Infrastructure/ApplicationTestFixture.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Precompilation.FunctionalTests/Infrastructure/ApplicationTestFixture.cs index 11b19c616d..a688632a81 100644 --- a/test/Microsoft.AspNetCore.Mvc.Razor.Precompilation.FunctionalTests/Infrastructure/ApplicationTestFixture.cs +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Precompilation.FunctionalTests/Infrastructure/ApplicationTestFixture.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Net.Http; using System.Runtime.InteropServices; using Microsoft.AspNetCore.Server.IntegrationTesting; using Microsoft.DotNet.Cli.Utils; @@ -32,6 +33,8 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Precompilation public string TempRestoreDirectory { get; } = CreateTempRestoreDirectory(); + public HttpClient HttpClient { get; } = new HttpClient(); + public ILogger Logger { get; private set; } public IApplicationDeployer CreateDeployment(RuntimeFlavor flavor) @@ -66,7 +69,6 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Precompilation { PublishApplicationBeforeDeployment = true, TargetFramework = flavor == RuntimeFlavor.Clr ? "net451" : "netcoreapp1.0", - PreservePublishedApplicationForDebugging = true, Configuration = "Release", EnvironmentVariables = { @@ -93,6 +95,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Precompilation public virtual void Dispose() { TryDeleteDirectory(TempRestoreDirectory); + HttpClient.Dispose(); } protected static void TryDeleteDirectory(string directory) diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Precompilation.FunctionalTests/PublishWithEmbedViewSourcesTest.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Precompilation.FunctionalTests/PublishWithEmbedViewSourcesTest.cs new file mode 100644 index 0000000000..609be6da99 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Precompilation.FunctionalTests/PublishWithEmbedViewSourcesTest.cs @@ -0,0 +1,81 @@ +// 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.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Server.IntegrationTesting; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.Razor.Precompilation.FunctionalTests +{ + public class PublishWithEmbedViewSourcesTest + : IClassFixture + { + private const string ApplicationName = "PublishWithEmbedViewSources"; + + public PublishWithEmbedViewSourcesTest(PublishWithEmbedViewSourcesTestFixture fixture) + { + Fixture = fixture; + } + + public ApplicationTestFixture Fixture { get; } + + public static IEnumerable SupportedFlavorsTheoryData + { + get + { + return RuntimeFlavors.SupportedFlavors.Select(f => new object[] { f }); + } + } + + [Theory] + [MemberData(nameof(SupportedFlavorsTheoryData))] + public async Task Precompilation_CanEmbedViewSourcesAsResources(RuntimeFlavor flavor) + { + // Arrange + var expectedViews = new[] + { + "Areas/TestArea/Views/Home/Index.cshtml", + "Views/Home/About.cshtml", + "Views/Home/Index.cshtml", + }; + var expectedText = "Hello Index!"; + using (var deployer = Fixture.CreateDeployment(flavor)) + { + var deploymentResult = deployer.Deploy(); + var assemblyPath = Path.Combine( + deploymentResult.DeploymentParameters.PublishedApplicationRootPath, + $"{ApplicationName}.PrecompiledViews.dll"); + + // Act - 1 + var response1 = await Fixture.HttpClient.GetStringWithRetryAsync( + $"{deploymentResult.ApplicationBaseUri}Home/Index", + Fixture.Logger); + + // Assert - 1 + Assert.Equal(expectedText, response1.Trim()); + + // Act - 2 + var response2 = await Fixture.HttpClient.GetStringWithRetryAsync( + $"{deploymentResult.ApplicationBaseUri}Home/GetPrecompiledResourceNames", + Fixture.Logger); + + // Assert - 2 + Assert.Equal( + expectedViews, + response2.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries)); + } + } + + public class PublishWithEmbedViewSourcesTestFixture : ApplicationTestFixture + { + public PublishWithEmbedViewSourcesTestFixture() + : base(PublishWithEmbedViewSourcesTest.ApplicationName) + { + } + } + } +} diff --git a/testapps/PublishWithEmbedViewSources/Areas/TestArea/Views/Home/Index.cshtml b/testapps/PublishWithEmbedViewSources/Areas/TestArea/Views/Home/Index.cshtml new file mode 100644 index 0000000000..a2ab94af53 --- /dev/null +++ b/testapps/PublishWithEmbedViewSources/Areas/TestArea/Views/Home/Index.cshtml @@ -0,0 +1 @@ +Hello Index! \ No newline at end of file diff --git a/testapps/PublishWithEmbedViewSources/Controllers/HomeController.cs b/testapps/PublishWithEmbedViewSources/Controllers/HomeController.cs new file mode 100644 index 0000000000..8d93240d44 --- /dev/null +++ b/testapps/PublishWithEmbedViewSources/Controllers/HomeController.cs @@ -0,0 +1,23 @@ +using System; +using System.Reflection; +using Microsoft.AspNetCore.Mvc; + +namespace PublishWithEmbedViewSources.Controllers +{ + public class HomeController : Controller + { + public IActionResult Index() + { + return View(); + } + + public string GetPrecompiledResourceNames() + { + var precompiledAssembly = Assembly.Load( + new AssemblyName("PublishWithEmbedViewSources.PrecompiledViews")); + return string.Join( + Environment.NewLine, + precompiledAssembly.GetManifestResourceNames()); + } + } +} diff --git a/testapps/PublishWithEmbedViewSources/Program.cs b/testapps/PublishWithEmbedViewSources/Program.cs new file mode 100644 index 0000000000..66e30e6cfe --- /dev/null +++ b/testapps/PublishWithEmbedViewSources/Program.cs @@ -0,0 +1,26 @@ +using System.IO; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; + +namespace PublishWithEmbedViewSources +{ + 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/PublishWithEmbedViewSources/PublishWithEmbedViewSources.xproj b/testapps/PublishWithEmbedViewSources/PublishWithEmbedViewSources.xproj new file mode 100644 index 0000000000..25dace6505 --- /dev/null +++ b/testapps/PublishWithEmbedViewSources/PublishWithEmbedViewSources.xproj @@ -0,0 +1,18 @@ + + + + 14.0.25420 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + e3462190-3068-40f0-9aa5-34779fe252ac + .\obj + .\bin\ + + + + 2.0 + + + \ No newline at end of file diff --git a/testapps/PublishWithEmbedViewSources/Startup.cs b/testapps/PublishWithEmbedViewSources/Startup.cs new file mode 100644 index 0000000000..c1e951b594 --- /dev/null +++ b/testapps/PublishWithEmbedViewSources/Startup.cs @@ -0,0 +1,26 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace PublishWithEmbedViewSources +{ + 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: "default", + template: "{controller=Home}/{action=Index}/{id?}"); + }); + } + } +} diff --git a/testapps/PublishWithEmbedViewSources/Views/Home/About.cshtml b/testapps/PublishWithEmbedViewSources/Views/Home/About.cshtml new file mode 100644 index 0000000000..b35b6196f4 --- /dev/null +++ b/testapps/PublishWithEmbedViewSources/Views/Home/About.cshtml @@ -0,0 +1 @@ +Hello About! \ No newline at end of file diff --git a/testapps/PublishWithEmbedViewSources/Views/Home/Index.cshtml b/testapps/PublishWithEmbedViewSources/Views/Home/Index.cshtml new file mode 100644 index 0000000000..a2ab94af53 --- /dev/null +++ b/testapps/PublishWithEmbedViewSources/Views/Home/Index.cshtml @@ -0,0 +1 @@ +Hello Index! \ No newline at end of file diff --git a/testapps/PublishWithEmbedViewSources/project.json b/testapps/PublishWithEmbedViewSources/project.json new file mode 100644 index 0000000000..f70de8c71d --- /dev/null +++ b/testapps/PublishWithEmbedViewSources/project.json @@ -0,0 +1,45 @@ +{ + "buildOptions": { + "emitEntryPoint": true, + "preserveCompilationContext": true + }, + "dependencies": { + "Microsoft.AspNetCore.Mvc": "1.1.0-*", + "Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design": { + "version": "1.0.0-*", + "target": "package", + "type": "build" + }, + "Microsoft.AspNetCore.Server.Kestrel": "1.1.0-*", + "Microsoft.Extensions.Configuration.CommandLine": "1.1.0-*", + "Microsoft.Extensions.Logging.Console": "1.1.0-*" + }, + + "tools": { + "Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools": "1.0.0-*" + }, + + "frameworks": { + "netcoreapp1.0": { + "dependencies": { + "Microsoft.NETCore.App": { + "version": "1.0.0", + "type": "platform" + } + } + }, + "net451": {} + }, + + "publishOptions": { + "include": [ + "wwwroot", + "appsettings.json", + "web.config" + ] + }, + + "scripts": { + "postpublish": "dotnet razor-precompile --embed-view-sources --configuration %publish:Configuration% --framework %publish:TargetFramework% --output-path %publish:OutputPath% %publish:ProjectPath%" + } +}