Add support for precompiling RazorPages

This commit is contained in:
Pranav K 2017-04-05 12:45:39 -07:00
parent 69e2fa82eb
commit 7f4aa60a45
27 changed files with 339 additions and 16 deletions

View File

@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.26228.4
VisualStudioVersion = 15.0.26329.2
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.Razor.ViewCompilation", "src\Microsoft.AspNetCore.Mvc.Razor.ViewCompilation\Microsoft.AspNetCore.Mvc.Razor.ViewCompilation.csproj", "{4339FC9B-AEC6-442A-B413-A41555ED76C7}"
EndProject
@ -51,6 +51,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ApplicationUsingPrecompiled
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ApplicationWithTagHelpers", "testapps\ApplicationWithTagHelpers\ApplicationWithTagHelpers.csproj", "{08552602-37E7-48A7-95A2-BB1A1F57C804}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RazorPagesApp", "testapps\RazorPagesApp\RazorPagesApp.csproj", "{779BACC4-B20E-4F73-A9C7-350443CF1941}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -117,6 +119,10 @@ Global
{08552602-37E7-48A7-95A2-BB1A1F57C804}.Debug|Any CPU.Build.0 = Debug|Any CPU
{08552602-37E7-48A7-95A2-BB1A1F57C804}.Release|Any CPU.ActiveCfg = Release|Any CPU
{08552602-37E7-48A7-95A2-BB1A1F57C804}.Release|Any CPU.Build.0 = Release|Any CPU
{779BACC4-B20E-4F73-A9C7-350443CF1941}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{779BACC4-B20E-4F73-A9C7-350443CF1941}.Debug|Any CPU.Build.0 = Debug|Any CPU
{779BACC4-B20E-4F73-A9C7-350443CF1941}.Release|Any CPU.ActiveCfg = Release|Any CPU
{779BACC4-B20E-4F73-A9C7-350443CF1941}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -137,5 +143,6 @@ Global
{68BB859F-E5D5-407E-9DFB-8CD478EFE90D} = {0DC7C88C-E3DB-46DF-B47E-AC5ECB2A16B7}
{037F4B73-75FB-4570-A38A-9109B580168C} = {0DC7C88C-E3DB-46DF-B47E-AC5ECB2A16B7}
{08552602-37E7-48A7-95A2-BB1A1F57C804} = {0DC7C88C-E3DB-46DF-B47E-AC5ECB2A16B7}
{779BACC4-B20E-4F73-A9C7-350443CF1941} = {0DC7C88C-E3DB-46DF-B47E-AC5ECB2A16B7}
EndGlobalSection
EndGlobal

View File

@ -9,15 +9,16 @@ using System.Text;
using Microsoft.AspNetCore.Mvc.ApplicationParts;
using Microsoft.AspNetCore.Mvc.Razor.Compilation;
using Microsoft.AspNetCore.Mvc.Razor.Internal;
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Text;
namespace Microsoft.AspNetCore.Mvc.Razor.ViewCompilation.Internal
{
internal class ViewInfoContainerCodeGenerator
internal class ManifestGenerator
{
public ViewInfoContainerCodeGenerator(
public ManifestGenerator(
CSharpCompiler compiler,
CSharpCompilation compilation)
{
@ -29,8 +30,35 @@ namespace Microsoft.AspNetCore.Mvc.Razor.ViewCompilation.Internal
public CSharpCompilation Compilation { get; private set; }
public void AddViewFactory(IList<ViewCompilationInfo> result)
public void GenerateManifest(IList<ViewCompilationInfo> results)
{
var views = new List<ViewCompilationInfo>();
var pages = new List<ViewCompilationInfo>();
for (var i = 0; i < results.Count; i++)
{
var result = results[i];
if (result.RouteTemplate != null)
{
pages.Add(result);
}
else
{
views.Add(result);
}
}
GeneratePageManifest(pages);
GenerateViewManifest(views);
}
private void GenerateViewManifest(IList<ViewCompilationInfo> result)
{
if (result.Count == 0)
{
return;
}
var precompiledViewsArray = new StringBuilder();
foreach (var item in result)
{
@ -56,6 +84,40 @@ namespace {ViewsFeatureProvider.ViewInfoContainerNamespace}
Compilation = Compilation.AddSyntaxTrees(syntaxTree);
}
private void GeneratePageManifest(IList<ViewCompilationInfo> pages)
{
if (pages.Count == 0)
{
return;
}
var precompiledViewsArray = new StringBuilder();
foreach (var item in pages)
{
var path = item.ViewFileInfo.ViewEnginePath;
var routeTemplate = item.RouteTemplate;
var escapedRouteTemplate = routeTemplate.Replace("\"", "\\\"");
precompiledViewsArray.AppendLine(
$"new global::{typeof(CompiledPageInfo).FullName}(@\"{path}\", typeof({item.TypeName}), \"{escapedRouteTemplate}\"),");
}
var factoryContent = $@"
namespace {CompiledPageFeatureProvider.CompiledPageManifestNamespace}
{{
public class {CompiledPageFeatureProvider.CompiledPageManifestTypeName} : global::{typeof(CompiledPageManifest).FullName}
{{
public {CompiledPageFeatureProvider.CompiledPageManifestTypeName}() : base(new[]
{{
{precompiledViewsArray}
}})
{{
}}
}}
}}";
var syntaxTree = Compiler.CreateSyntaxTree(SourceText.From(factoryContent));
Compilation = Compilation.AddSyntaxTrees(syntaxTree);
}
public void AddAssemblyMetadata(
AssemblyName applicationAssemblyName,
CompilationOptions compilationOptions)

View File

@ -9,7 +9,10 @@ using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Razor.Compilation;
using Microsoft.AspNetCore.Mvc.Razor.Extensions;
using Microsoft.AspNetCore.Mvc.Razor.Internal;
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
@ -75,7 +78,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.ViewCompilation.Internal
return 1;
}
var precompileAssemblyName = $"{Options.ApplicationName}{ViewsFeatureProvider.PrecompiledViewsAssemblySuffix}";
var precompileAssemblyName = $"{Options.ApplicationName}{CompiledViewManfiest.PrecompiledViewsAssemblySuffix}";
var compilation = CompileViews(results, precompileAssemblyName);
var resources = GetResources(results);
@ -189,8 +192,8 @@ namespace Microsoft.AspNetCore.Mvc.Razor.ViewCompilation.Internal
MvcServiceProvider.ViewEngineOptions.CompilationCallback(compilationContext);
compilation = compilationContext.Compilation;
var codeGenerator = new ViewInfoContainerCodeGenerator(compiler, compilation);
codeGenerator.AddViewFactory(results);
var codeGenerator = new ManifestGenerator(compiler, compilation);
codeGenerator.GenerateManifest(results);
var assemblyName = new AssemblyName(Options.ApplicationName);
assemblyName = Assembly.Load(assemblyName).GetName();
@ -236,11 +239,20 @@ namespace Microsoft.AspNetCore.Mvc.Razor.ViewCompilation.Internal
Parallel.For(0, results.Length, ParalellOptions, i =>
{
var fileInfo = files[i];
var templateEngine = MvcServiceProvider.TemplateEngine;
ViewCompilationInfo compilationInfo;
using (var fileStream = fileInfo.CreateReadStream())
{
var csharpDocument = MvcServiceProvider.TemplateEngine.GenerateCode(fileInfo.ViewEnginePath);
results[i] = new ViewCompilationInfo(fileInfo, csharpDocument);
var csharpDocument = templateEngine.GenerateCode(fileInfo.ViewEnginePath);
compilationInfo = new ViewCompilationInfo(fileInfo, csharpDocument);
}
if (PageDirectiveFeature.TryGetPageDirective(fileInfo.CreateReadStream, out var template))
{
compilationInfo.RouteTemplate = template;
}
results[i] = compilationInfo;
});
return results;
@ -250,7 +262,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.ViewCompilation.Internal
{
var contentRoot = Options.ContentRootOption.Value();
var viewFiles = Options.ViewsToCompile;
var relativeFiles = new List<ViewFileInfo>(viewFiles.Count);
var viewFileInfo = new List<ViewFileInfo>(viewFiles.Count);
var trimLength = contentRoot.EndsWith("/") ? contentRoot.Length - 1 : contentRoot.Length;
for (var i = 0; i < viewFiles.Count; i++)
@ -259,11 +271,11 @@ namespace Microsoft.AspNetCore.Mvc.Razor.ViewCompilation.Internal
if (fullPath.StartsWith(contentRoot, StringComparison.OrdinalIgnoreCase))
{
var viewEnginePath = fullPath.Substring(trimLength).Replace('\\', '/');
relativeFiles.Add(new ViewFileInfo(fullPath, viewEnginePath));
viewFileInfo.Add(new ViewFileInfo(fullPath, viewEnginePath));
}
}
return relativeFiles;
return viewFileInfo;
}
private string ReadTypeInfo(CSharpCompilation compilation, SyntaxTree syntaxTree)

View File

@ -1,6 +1,7 @@
// 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 Microsoft.AspNetCore.Mvc.Razor.Extensions;
using Microsoft.AspNetCore.Razor.Language;
namespace Microsoft.AspNetCore.Mvc.Razor.ViewCompilation.Internal
@ -20,5 +21,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.ViewCompilation.Internal
public RazorCSharpDocument CSharpDocument { get; }
public string TypeName { get; set; }
public string RouteTemplate { get; set; }
}
}

View File

@ -18,7 +18,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Hosting" Version="$(AspNetCoreVersion)" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor" Version="$(AspNetCoreVersion)" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.RazorPages" Version="$(AspNetCoreVersion)" />
<PackageReference Include="Microsoft.Extensions.CommandLineUtils.Sources" Version="$(AspNetCoreVersion)" PrivateAssets="All" />
</ItemGroup>

View File

@ -19,15 +19,17 @@ namespace Microsoft.AspNetCore.Mvc.Razor.ViewCompilation
{
ApplicationName = applicationName;
DeploymentResult = CreateDeployment();
HttpClient = new HttpClient();
HttpClient.BaseAddress = new Uri(DeploymentResult.ApplicationBaseUri);
HttpClient = new HttpClient
{
BaseAddress = new Uri(DeploymentResult.ApplicationBaseUri),
};
}
public string ApplicationName { get; }
public string ApplicationPath => ApplicationPaths.GetTestAppDirectory(ApplicationName);
public HttpClient HttpClient { get; }
public HttpClient HttpClient { get; private set; }
public ILogger Logger { get; private set; }

View File

@ -5,6 +5,7 @@
<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
<DefineConstants>$(DefineConstants);__remove_this_to__GENERATE_BASELINES</DefineConstants>
<DefineConstants Condition="'$(GenerateBaseLines)'=='true'">$(DefineConstants);GENERATE_BASELINES</DefineConstants>
</PropertyGroup>
<ItemGroup>

View File

@ -0,0 +1,100 @@
// 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.Threading.Tasks;
using Microsoft.AspNetCore.Server.IntegrationTesting;
using Xunit;
namespace Microsoft.AspNetCore.Mvc.Razor.ViewCompilation
{
public class RazorPagesAppTest : IClassFixture<RazorPagesAppTest.TestFixture>
{
public RazorPagesAppTest(TestFixture fixture)
{
Fixture = fixture;
}
public ApplicationTestFixture Fixture { get; }
[Fact]
public async Task Precompilation_WorksForIndexPage_UsingFolderName()
{
// Act
var response = await Fixture.HttpClient.GetStringWithRetryAsync(
"/",
Fixture.Logger);
// Assert
TestEmbeddedResource.AssertContent("RazorPages.Index.txt", response);
}
[Fact]
public async Task Precompilation_WorksForIndexPage_UsingFileName()
{
// Act
var response = await Fixture.HttpClient.GetStringWithRetryAsync(
"/Index",
Fixture.Logger);
// Assert
TestEmbeddedResource.AssertContent("RazorPages.Index.txt", response);
}
[Fact]
public async Task Precompilation_WorksForPageWithModel()
{
// Act
var response = await Fixture.HttpClient.GetStringWithRetryAsync(
"/PageWithModel?person=Dan",
Fixture.Logger);
// Assert
TestEmbeddedResource.AssertContent("RazorPages.PageWithModel.txt", response);
}
[Fact]
public async Task Precompilation_WorksForPageWithRoute()
{
// Act
var response = await Fixture.HttpClient.GetStringWithRetryAsync(
"/PageWithRoute/Dan",
Fixture.Logger);
// Assert
TestEmbeddedResource.AssertContent("RazorPages.PageWithRoute.txt", response);
}
[Fact]
public async Task Precompilation_WorksForPageInNestedFolder()
{
// Act
var response = await Fixture.HttpClient.GetStringWithRetryAsync(
"/Nested1/Nested2/PageWithTagHelper",
Fixture.Logger);
// Assert
TestEmbeddedResource.AssertContent("RazorPages.Nested1.Nested2.PageWithTagHelper.txt", response);
}
[Fact]
public async Task Precompilation_WorksWithPageConventions()
{
// Act
var response = await RetryHelper.RetryRequest(
() => Fixture.HttpClient.GetAsync("/Auth/Index"),
Fixture.Logger,
retryCount: 5);
// Assert
Assert.Equal("/Login?ReturnUrl=%2FAuth%2FIndex", response.RequestMessage.RequestUri.PathAndQuery);
}
public class TestFixture : ApplicationTestFixture
{
public TestFixture()
: base("RazorPagesApp")
{
}
}
}
}

View File

@ -0,0 +1,2 @@
<link rel="stylesheet" href="https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.6/css/bootstrap.min.css" />
<meta name="x-stylesheet-fallback-test" content="" class="sr-only" /><script>!function(a,b,c,d){var e,f=document,g=f.getElementsByTagName("SCRIPT"),h=g[g.length-1].previousElementSibling,i=f.defaultView&&f.defaultView.getComputedStyle?f.defaultView.getComputedStyle(h):h.currentStyle;if(i&&i[a]!==b)for(e=0;e<c.length;e++)f.write('<link href="'+c[e]+'" '+d+"/>")}("position","absolute",["\/lib\/bootstrap\/dist\/css\/bootstrap.min.css"], "rel=\u0022stylesheet\u0022 ");</script>

View File

@ -1,4 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\build\dependencies.props" />
<PropertyGroup>
<TargetFramework>netstandard1.6</TargetFramework>
</PropertyGroup>

View File

@ -0,0 +1,2 @@
@page
Can't see me

View File

@ -0,0 +1,3 @@
@page
Hello world!

View File

@ -0,0 +1,3 @@
@page
Hello world!

View File

@ -0,0 +1,16 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace RazorPagesApp
{
public class MyPageModel : PageModel
{
public string Name { get; private set; }
public IActionResult OnGet(string person)
{
Name = person;
return View();
}
}
}

View File

@ -0,0 +1,4 @@
@page
<link rel="stylesheet" href="https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.6/css/bootstrap.min.css"
asp-fallback-href="~/lib/bootstrap/dist/css/bootstrap.min.css"
asp-fallback-test-class="sr-only" asp-fallback-test-property="position" asp-fallback-test-value="absolute" />

View File

@ -0,0 +1 @@
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

View File

@ -0,0 +1,4 @@
@page
@model MyPageModel
Greetings @Model.Name!

View File

@ -0,0 +1,4 @@
@page "{person}"
@model MyPageModel
Greetings @Model.Name!

View File

@ -0,0 +1,3 @@
@{
Layout = "_Layout";
}

View File

@ -0,0 +1 @@
@using RazorPagesApp

View File

@ -0,0 +1,26 @@
using System.IO;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
namespace RazorPagesApp
{
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 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
<MvcRazorCompileOnPublish>true</MvcRazorCompileOnPublish>
</PropertyGroup>
<Import Project="..\..\build\common-testapps.props" />
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authentication.Cookies" Version="$(AspNetCoreVersion)" />
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="$(AspNetCoreVersion)" />
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="$(AspNetCoreVersion)" />
<PackageReference Include="Microsoft.Extensions.Configuration.CommandLine" Version="$(AspNetCoreVersion)" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="$(AspNetCoreVersion)" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,38 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace RazorPagesApp
{
public class Startup : IDesignTimeMvcBuilderConfiguration
{
public void ConfigureServices(IServiceCollection services)
{
var builder = services.AddMvc();
ConfigureMvc(builder);
}
public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole();
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
LoginPath = "/Login",
AutomaticAuthenticate = true,
AutomaticChallenge = true
});
app.UseMvc();
}
public void ConfigureMvc(IMvcBuilder builder)
{
builder.AddRazorPagesOptions(options =>
{
options.RootDirectory = "/Pages";
options.AuthorizeFolder("/Auth");
});
}
}
}

View File

@ -0,0 +1,3 @@
<layout>
@RenderBody()
</layout>