Add an option to embed sources for compiled views (cshtml content) in the generated assembly

Fixes #2
This commit is contained in:
Pranav K 2016-09-01 17:05:46 -07:00
parent b4b35138c5
commit d046091727
14 changed files with 275 additions and 3 deletions

View File

@ -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

View File

@ -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);
}
}
}

View File

@ -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);
}
}

View File

@ -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))
{

View File

@ -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)

View File

@ -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<PublishWithEmbedViewSourcesTest.PublishWithEmbedViewSourcesTestFixture>
{
private const string ApplicationName = "PublishWithEmbedViewSources";
public PublishWithEmbedViewSourcesTest(PublishWithEmbedViewSourcesTestFixture fixture)
{
Fixture = fixture;
}
public ApplicationTestFixture Fixture { get; }
public static IEnumerable<object[]> 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)
{
}
}
}
}

View File

@ -0,0 +1 @@
Hello Index!

View File

@ -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());
}
}
}

View File

@ -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<Startup>()
.Build();
host.Run();
}
}
}

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0.25420" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0.25420</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<ProjectGuid>e3462190-3068-40f0-9aa5-34779fe252ac</ProjectGuid>
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">.\obj</BaseIntermediateOutputPath>
<OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath>
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>

View File

@ -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?}");
});
}
}
}

View File

@ -0,0 +1 @@
Hello About!

View File

@ -0,0 +1 @@
Hello Index!

View File

@ -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%"
}
}