Skip to content
This repository was archived by the owner on Dec 14, 2018. It is now read-only.

Commit 130e1f9

Browse files
author
YishaiGalatzer
committed
Create a pre compilation module and apis to allow meta programming
to precompile razor pages. This is limited to sites where the .cshtml are still deployed. It's current purpose is to speed up startup. Deploying without the razor files is a separate feature.
1 parent 43c7ddb commit 130e1f9

22 files changed

+739
-49
lines changed

src/Microsoft.AspNet.Mvc.Razor.Host/IMvcRazorHost.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,15 @@ namespace Microsoft.AspNet.Mvc.Razor
99
public interface IMvcRazorHost
1010
{
1111
GeneratorResults GenerateCode(string rootRelativePath, Stream inputStream);
12+
13+
/// <summary>
14+
/// Represent the prefix off the main entry class in the view.
15+
/// </summary>
16+
string MainClassNamePrefix { get; }
17+
18+
/// <summary>
19+
/// Represent the namespace the main entry class in the view.
20+
/// </summary>
21+
string DefaultNamespace { get; }
1222
}
1323
}

src/Microsoft.AspNet.Mvc.Razor.Host/MvcRazorHost.cs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,6 @@ public MvcRazorHost(IApplicationEnvironment appEnvironment)
5858
public MvcRazorHost(string root) :
5959
this(new PhysicalFileSystem(root))
6060
{
61-
6261
}
6362
#endif
6463

@@ -101,6 +100,12 @@ public virtual string DefaultModel
101100
get { return "dynamic"; }
102101
}
103102

103+
/// <inheritdoc />
104+
public string MainClassNamePrefix
105+
{
106+
get { return "ASPV_"; }
107+
}
108+
104109
/// <summary>
105110
/// Gets the list of chunks that are injected by default by this host.
106111
/// </summary>
@@ -121,7 +126,8 @@ public virtual string ActivateAttribute
121126
/// <inheritdoc />
122127
public GeneratorResults GenerateCode(string rootRelativePath, Stream inputStream)
123128
{
124-
var className = ParserHelpers.SanitizeClassName(rootRelativePath);
129+
// Adding a prefix so that the main view class can be easily identified.
130+
var className = MainClassNamePrefix + ParserHelpers.SanitizeClassName(rootRelativePath);
125131
using (var reader = new StreamReader(inputStream))
126132
{
127133
var engine = new RazorTemplateEngine(this);

src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilationResult.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ private static string ReadContent(IFileInfo file)
120120
}
121121
}
122122
}
123-
catch (IOException)
123+
catch (Exception)
124124
{
125125
// Don't throw if reading the file fails.
126126
return string.Empty;

src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilerCache.cs

Lines changed: 81 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,34 +3,102 @@
33

44
using System;
55
using System.Collections.Concurrent;
6-
using Microsoft.AspNet.FileSystems;
6+
using System.Linq;
7+
using System.Reflection;
78

89
namespace Microsoft.AspNet.Mvc.Razor
910
{
1011
public class CompilerCache
1112
{
12-
private readonly ConcurrentDictionary<string, Type> _cache;
13+
public static bool DebugBreak { get; set; }
14+
private readonly ConcurrentDictionary<string, CompilerCacheEntry> _cache;
15+
private static readonly Type[] EmptyType = new Type[0];
1316

14-
public CompilerCache()
17+
internal CompilerCache()
1518
{
16-
_cache = new ConcurrentDictionary<string, Type>(StringComparer.OrdinalIgnoreCase);
19+
_cache = new ConcurrentDictionary<string, CompilerCacheEntry>(StringComparer.OrdinalIgnoreCase);
1720
}
1821

19-
public CompilationResult GetOrAdd(IFileInfo file, Func<CompilationResult> compile)
22+
public CompilerCache([NotNull] IControllerAssemblyProvider _controllerAssemblyProvider)
23+
: this()
2024
{
21-
// Generate a content id
22-
var contentId = file.PhysicalPath + '|' + file.LastModified.Ticks;
25+
var assemblies = _controllerAssemblyProvider.CandidateAssemblies;
26+
var types = assemblies.SelectMany(a => a.ExportedTypes);
27+
var preCompiledCollections =
28+
types
29+
.Where(Match);
2330

24-
Type compiledType;
25-
if (!_cache.TryGetValue(contentId, out compiledType))
31+
foreach (var collectionType in preCompiledCollections)
2632
{
27-
var result = compile();
28-
_cache.TryAdd(contentId, result.CompiledType);
33+
var preCompiledCollection = Activator.CreateInstance(collectionType)
34+
as ViewDescriptorCollection;
2935

30-
return result;
36+
foreach (var fileInfo in preCompiledCollection.FileInfos)
37+
{
38+
var containingAssembly = collectionType.GetTypeInfo().Assembly;
39+
var viewType = containingAssembly.GetType(fileInfo.FullTypeName);
40+
var cacheEntry = new CompilerCacheEntry(fileInfo, viewType);
41+
42+
_cache.AddOrUpdate(fileInfo.RelativePath, cacheEntry, (a, b) => cacheEntry);
43+
}
3144
}
45+
}
46+
47+
private static bool Match(Type t)
48+
{
49+
var b = t.GetConstructor(EmptyType) != null;
50+
51+
var inAssemblyType = typeof(ViewDescriptorCollection);
52+
var c = inAssemblyType.IsAssignableFrom(t);
53+
54+
return b && c
55+
&& !t.GetTypeInfo().IsAbstract
56+
&& !t.GetTypeInfo().ContainsGenericParameters;
57+
}
58+
59+
public CompilationResult GetOrAdd(RelativeFileInfo fileInfo, Func<CompilationResult> compile)
60+
{
61+
CompilerCacheEntry cacheEntry;
62+
63+
if (!_cache.TryGetValue(fileInfo.RelativePath, out cacheEntry))
64+
{
65+
return OnCacheMiss(fileInfo, compile);
66+
}
67+
else
68+
{
69+
if (cacheEntry.Length != fileInfo.FileInfo.Length)
70+
{
71+
// it's not a match, recompile
72+
return OnCacheMiss(fileInfo, compile);
73+
}
74+
75+
if (cacheEntry.RuntimeTimeStamp == fileInfo.FileInfo.LastModified)
76+
{
77+
// Match, not update needed
78+
return CompilationResult.Successful(cacheEntry.ViewType);
79+
}
80+
81+
if (cacheEntry.CompiledTimeStamp == fileInfo.FileInfo.LastModified ||
82+
// Date doesn't match but it might be because of deployment, compare the hash
83+
cacheEntry.Hash == RazorFileHash.GetHash(fileInfo.FileInfo))
84+
{
85+
// Cache hit, but we need to update the entry
86+
return OnCacheMiss(fileInfo, () => CompilationResult.Successful(cacheEntry.ViewType));
87+
}
88+
89+
// it's not a match, recompile
90+
return OnCacheMiss(fileInfo, compile);
91+
}
92+
}
93+
94+
private CompilationResult OnCacheMiss(RelativeFileInfo file, Func<CompilationResult> compile)
95+
{
96+
var result = compile();
97+
98+
var cacheEntry = new CompilerCacheEntry(file, result.CompiledType, null);
99+
_cache.AddOrUpdate(file.RelativePath, cacheEntry, (a, b) => cacheEntry);
32100

33-
return CompilationResult.Successful(compiledType);
101+
return result;
34102
}
35103
}
36104
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
6+
namespace Microsoft.AspNet.Mvc.Razor
7+
{
8+
public class CompilerCacheEntry
9+
{
10+
public CompilerCacheEntry([NotNull] RazorFileInfo info, [NotNull] Type viewType)
11+
{
12+
ViewType = viewType;
13+
RelativePath = info.RelativePath;
14+
Length = info.Length;
15+
CompiledTimeStamp = info.LastModified;
16+
Hash = info.Hash;
17+
}
18+
19+
public CompilerCacheEntry([NotNull] RelativeFileInfo info, [NotNull] Type viewType, string hash)
20+
{
21+
ViewType = viewType;
22+
RelativePath = info.RelativePath;
23+
Length = info.FileInfo.Length;
24+
CompiledTimeStamp = info.FileInfo.LastModified;
25+
RuntimeTimeStamp = info.FileInfo.LastModified;
26+
Hash = hash;
27+
}
28+
29+
public Type ViewType { get; set; }
30+
public string RelativePath { get; set; }
31+
public long Length { get; set; }
32+
public DateTime CompiledTimeStamp { get; set; }
33+
public DateTime? RuntimeTimeStamp { get; set; }
34+
public string Hash { get; set; }
35+
}
36+
}

src/Microsoft.AspNet.Mvc.Razor/Compilation/RoslynCompilationService.cs

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,27 +31,31 @@ public class RoslynCompilationService : ICompilationService
3131

3232
private readonly Lazy<List<MetadataReference>> _applicationReferences;
3333

34+
private readonly string _classPrefix;
35+
3436
/// <summary>
3537
/// Initalizes a new instance of the <see cref="RoslynCompilationService"/> class.
3638
/// </summary>
3739
/// <param name="environment">The environment for the executing application.</param>
3840
/// <param name="loaderEngine">The loader used to load compiled assemblies.</param>
3941
/// <param name="libraryManager">The library manager that provides export and reference information.</param>
42+
/// <param name="host">The <see cref="IMvcRazorHost"/> that was used to generate the code.</param>
4043
public RoslynCompilationService(IApplicationEnvironment environment,
4144
IAssemblyLoaderEngine loaderEngine,
42-
ILibraryManager libraryManager)
45+
ILibraryManager libraryManager,
46+
IMvcRazorHost host)
4347
{
4448
_environment = environment;
4549
_loader = loaderEngine;
4650
_libraryManager = libraryManager;
4751
_applicationReferences = new Lazy<List<MetadataReference>>(GetApplicationReferences);
52+
_classPrefix = host.MainClassNamePrefix;
4853
}
4954

5055
/// <inheritdoc />
5156
public CompilationResult Compile(IFileInfo fileInfo, string compilationContent)
5257
{
53-
var sourceText = SourceText.From(compilationContent, Encoding.UTF8);
54-
var syntaxTrees = new[] { CSharpSyntaxTree.ParseText(sourceText, path: fileInfo.PhysicalPath) };
58+
var syntaxTrees = new[] { SyntaxTreeGenerator.Generate(compilationContent, fileInfo) };
5559

5660
var references = _applicationReferences.Value;
5761

@@ -103,9 +107,10 @@ public CompilationResult Compile(IFileInfo fileInfo, string compilationContent)
103107
}
104108

105109
var type = assembly.GetExportedTypes()
106-
.First();
110+
.First(t => t.Name.
111+
StartsWith(_classPrefix, StringComparison.Ordinal));
107112

108-
return UncachedCompilationResult.Successful(type, compilationContent);
113+
return UncachedCompilationResult.Successful(type);
109114
}
110115
}
111116
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System.Text;
5+
using Microsoft.AspNet.FileSystems;
6+
using Microsoft.CodeAnalysis;
7+
using Microsoft.CodeAnalysis.CSharp;
8+
using Microsoft.CodeAnalysis.Text;
9+
10+
namespace Microsoft.AspNet.Mvc.Razor
11+
{
12+
public static class SyntaxTreeGenerator
13+
{
14+
private static CSharpParseOptions DefaultOptions
15+
{
16+
get
17+
{
18+
return CSharpParseOptions.Default
19+
.WithLanguageVersion(LanguageVersion.CSharp6);
20+
}
21+
}
22+
23+
public static SyntaxTree Generate([NotNull] string text, [NotNull] IFileInfo fileInfo)
24+
{
25+
return GenerateCore(text, fileInfo.PhysicalPath, DefaultOptions);
26+
}
27+
28+
public static SyntaxTree Generate([NotNull] string text,
29+
[NotNull] IFileInfo fileInfo,
30+
[NotNull] CSharpParseOptions options)
31+
{
32+
return GenerateCore(text, fileInfo.PhysicalPath, options);
33+
}
34+
35+
public static SyntaxTree Generate([NotNull] string text, [NotNull] string path)
36+
{
37+
return GenerateCore(text, path, DefaultOptions);
38+
}
39+
40+
public static SyntaxTree Generate([NotNull] string text,
41+
[NotNull] string path,
42+
[NotNull] CSharpParseOptions options)
43+
{
44+
return GenerateCore(text, path, options);
45+
}
46+
47+
public static SyntaxTree GenerateCore([NotNull] string text,
48+
[NotNull] string path,
49+
[NotNull] CSharpParseOptions options)
50+
{
51+
var sourceText = SourceText.From(text, Encoding.UTF8);
52+
var syntaxTree = CSharpSyntaxTree.ParseText(sourceText,
53+
path: path,
54+
options: options);
55+
56+
return syntaxTree;
57+
}
58+
}
59+
}

src/Microsoft.AspNet.Mvc.Razor/Compilation/UncachedCompilationResult.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ private UncachedCompilationResult()
1414
{
1515
}
1616

17+
public string RazorFileContent { get; private set; }
18+
1719
/// <summary>
1820
/// Creates a <see cref="UncachedCompilationResult"/> that represents a success in compilation.
1921
/// </summary>
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using Microsoft.CodeAnalysis.CSharp;
5+
6+
namespace Microsoft.AspNet.Mvc.Razor
7+
{
8+
internal static class ParseOptions
9+
{
10+
public static CSharpParseOptions GetParseOptions(CSharpCompilation compilation)
11+
{
12+
return CSharpParseOptions.Default
13+
.WithLanguageVersion(compilation.LanguageVersion);
14+
}
15+
}
16+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
6+
namespace Microsoft.AspNet.Mvc.Razor
7+
{
8+
public class RazorFileInfo
9+
{
10+
/// <summary>
11+
/// Type name including namespace.
12+
/// </summary>
13+
public string FullTypeName { get; set; }
14+
15+
/// <summary>
16+
/// Last modified at compilation time.
17+
/// </summary>
18+
public DateTime LastModified { get; set; }
19+
20+
/// <summary>
21+
/// The length of the file in bytes.
22+
/// </summary>
23+
public long Length { get; set; }
24+
25+
/// <summary>
26+
/// Path to to the file relative to the application base.
27+
/// </summary>
28+
public string RelativePath { get; set; }
29+
30+
/// <summary>
31+
/// A hash of the file content.
32+
/// </summary>
33+
public string Hash { get; set; }
34+
}
35+
}

0 commit comments

Comments
 (0)