diff --git a/src/Microsoft.AspNet.Mvc.Razor/Razor/PreCompileViews/PrecompilationCacheEntry.cs b/src/Microsoft.AspNet.Mvc.Razor/Razor/PreCompileViews/PrecompilationCacheEntry.cs
new file mode 100644
index 0000000000..e7a53d2509
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.Razor/Razor/PreCompileViews/PrecompilationCacheEntry.cs
@@ -0,0 +1,71 @@
+// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Collections.Generic;
+using Microsoft.CodeAnalysis;
+
+namespace Microsoft.AspNet.Mvc.Razor
+{
+ ///
+ /// An entry in the cache used by .
+ ///
+ public class PrecompilationCacheEntry
+ {
+ ///
+ /// Initializes a new instance of for a successful parse.
+ ///
+ /// The of the file being cached.
+ /// The to cache.
+ public PrecompilationCacheEntry([NotNull] RazorFileInfo fileInfo,
+ [NotNull] SyntaxTree syntaxTree)
+ {
+ FileInfo = fileInfo;
+ SyntaxTree = syntaxTree;
+ }
+
+ ///
+ /// Initializes a new instance of for a failed parse.
+ ///
+ /// The produced from parsing the Razor
+ /// file. This does not contain s produced from compiling the parsed
+ /// .
+ public PrecompilationCacheEntry([NotNull] IReadOnlyList diagnostics)
+ {
+ Diagnostics = diagnostics;
+ }
+
+ ///
+ /// Gets the associated with this cache entry instance.
+ ///
+ ///
+ /// This property is not null if is true.
+ ///
+ public RazorFileInfo FileInfo { get; }
+
+ ///
+ /// Gets the produced from parsing the Razor file.
+ ///
+ ///
+ /// This property is not null if is true.
+ ///
+ public SyntaxTree SyntaxTree { get; }
+
+ ///
+ /// Gets the s produced from parsing the generated contents of the file
+ /// specified by . This does not contain s produced from
+ /// compiling the parsed .
+ ///
+ ///
+ /// This property is null if is true.
+ ///
+ public IReadOnlyList Diagnostics { get; }
+
+ ///
+ /// Gets a value that indicates if parsing was successful.
+ ///
+ public bool Success
+ {
+ get { return SyntaxTree != null; }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Mvc.Razor/Razor/PreCompileViews/RazorFileInfoCollectionGenerator.cs b/src/Microsoft.AspNet.Mvc.Razor/Razor/PreCompileViews/RazorFileInfoCollectionGenerator.cs
index 5dc2b51c4a..4db7acad5d 100644
--- a/src/Microsoft.AspNet.Mvc.Razor/Razor/PreCompileViews/RazorFileInfoCollectionGenerator.cs
+++ b/src/Microsoft.AspNet.Mvc.Razor/Razor/PreCompileViews/RazorFileInfoCollectionGenerator.cs
@@ -12,16 +12,17 @@ namespace Microsoft.AspNet.Mvc.Razor
{
private string _fileFormat;
- protected IReadOnlyList FileInfos { get; private set; }
- protected CompilationSettings CompilationSettings { get; }
-
- public RazorFileInfoCollectionGenerator([NotNull] IReadOnlyList fileInfos,
+ public RazorFileInfoCollectionGenerator([NotNull] IEnumerable fileInfos,
[NotNull] CompilationSettings compilationSettings)
{
FileInfos = fileInfos;
CompilationSettings = compilationSettings;
}
+ protected IEnumerable FileInfos { get; }
+
+ protected CompilationSettings CompilationSettings { get; }
+
public virtual SyntaxTree GenerateCollection()
{
var builder = new StringBuilder();
diff --git a/src/Microsoft.AspNet.Mvc.Razor/Razor/PreCompileViews/RazorPreCompiler.cs b/src/Microsoft.AspNet.Mvc.Razor/Razor/PreCompileViews/RazorPreCompiler.cs
index 0b59bc46a2..4bbe5b90a5 100644
--- a/src/Microsoft.AspNet.Mvc.Razor/Razor/PreCompileViews/RazorPreCompiler.cs
+++ b/src/Microsoft.AspNet.Mvc.Razor/Razor/PreCompileViews/RazorPreCompiler.cs
@@ -4,8 +4,11 @@
using System;
using System.Collections.Generic;
using System.IO;
+using System.Linq;
+using System.Threading.Tasks;
using Microsoft.AspNet.FileSystems;
-using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis;
+using Microsoft.Framework.Cache.Memory;
using Microsoft.Framework.DependencyInjection;
using Microsoft.Framework.OptionsModel;
using Microsoft.Framework.Runtime;
@@ -17,37 +20,42 @@ namespace Microsoft.AspNet.Mvc.Razor
{
private readonly IServiceProvider _serviceProvider;
private readonly IFileSystem _fileSystem;
- private readonly IMvcRazorHost _host;
public RazorPreCompiler([NotNull] IServiceProvider designTimeServiceProvider,
+ [NotNull] IMemoryCache precompilationCache,
[NotNull] CompilationSettings compilationSettings) :
this(designTimeServiceProvider,
- designTimeServiceProvider.GetRequiredService(),
designTimeServiceProvider.GetRequiredService>(),
+ precompilationCache,
compilationSettings)
{
}
public RazorPreCompiler([NotNull] IServiceProvider designTimeServiceProvider,
- [NotNull] IMvcRazorHost host,
[NotNull] IOptions optionsAccessor,
+ [NotNull] IMemoryCache precompilationCache,
[NotNull] CompilationSettings compilationSettings)
{
_serviceProvider = designTimeServiceProvider;
- _host = host;
_fileSystem = optionsAccessor.Options.FileSystem;
CompilationSettings = compilationSettings;
+ PreCompilationCache = precompilationCache;
}
protected CompilationSettings CompilationSettings { get; }
+ protected IMemoryCache PreCompilationCache { get; }
+
protected virtual string FileExtension { get; } = ".cshtml";
+ protected virtual int MaxDegreesOfParallelism { get; } = Environment.ProcessorCount;
+
+
public virtual void CompileViews([NotNull] IBeforeCompileContext context)
{
var descriptors = CreateCompilationDescriptors(context);
- if (descriptors.Count > 0)
+ if (descriptors.Any())
{
var collectionGenerator = new RazorFileInfoCollectionGenerator(
descriptors,
@@ -58,93 +66,127 @@ namespace Microsoft.AspNet.Mvc.Razor
}
}
- protected virtual IReadOnlyList CreateCompilationDescriptors(
- [NotNull] IBeforeCompileContext context)
+ protected virtual IEnumerable CreateCompilationDescriptors(
+ [NotNull] IBeforeCompileContext context)
{
- var list = new List();
+ var filesToProcess = new List();
+ GetFileInfosRecursive(root: string.Empty, razorFiles: filesToProcess);
- foreach (var info in GetFileInfosRecursive(string.Empty))
+ var razorFiles = new RazorFileInfo[filesToProcess.Count];
+ var syntaxTrees = new SyntaxTree[filesToProcess.Count];
+ var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = MaxDegreesOfParallelism };
+ var diagnosticsLock = new object();
+
+ Parallel.For(0, filesToProcess.Count, parallelOptions, index =>
{
- var descriptor = ParseView(info, context);
-
- if (descriptor != null)
+ var file = filesToProcess[index];
+ var cacheEntry = PreCompilationCache.GetOrSet(file.RelativePath,
+ file,
+ OnCacheMiss);
+ if (cacheEntry != null)
{
- list.Add(descriptor);
+ if (cacheEntry.Success)
+ {
+ syntaxTrees[index] = cacheEntry.SyntaxTree;
+ razorFiles[index] = cacheEntry.FileInfo;
+ }
+ else
+ {
+ lock (diagnosticsLock)
+ {
+ foreach (var diagnostic in cacheEntry.Diagnostics)
+ {
+ context.Diagnostics.Add(diagnostic);
+ }
+ }
+ }
+ }
+ });
+
+ context.CSharpCompilation = context.CSharpCompilation
+ .AddSyntaxTrees(syntaxTrees.Where(tree => tree != null));
+ return razorFiles.Where(file => file != null);
+ }
+
+ protected IMvcRazorHost GetRazorHost()
+ {
+ return _serviceProvider.GetRequiredService();
+ }
+
+ private PrecompilationCacheEntry OnCacheMiss(ICacheSetContext cacheSetContext)
+ {
+ var fileInfo = (RelativeFileInfo)cacheSetContext.State;
+ var entry = GetCacheEntry(fileInfo);
+
+ if (entry != null)
+ {
+ cacheSetContext.AddExpirationTrigger(_fileSystem.Watch(fileInfo.RelativePath));
+ foreach (var viewStartPath in ViewStartUtility.GetViewStartLocations(fileInfo.RelativePath))
+ {
+ cacheSetContext.AddExpirationTrigger(_fileSystem.Watch(viewStartPath));
}
}
- return list;
+ return entry;
}
- private IEnumerable GetFileInfosRecursive(string currentPath)
+ private void GetFileInfosRecursive(string root, List razorFiles)
{
- var path = currentPath;
-
- var fileInfos = _fileSystem.GetDirectoryContents(path);
- if (!fileInfos.Exists)
- {
- yield break;
- }
+ var fileInfos = _fileSystem.GetDirectoryContents(root);
foreach (var fileInfo in fileInfos)
{
if (fileInfo.IsDirectory)
{
- var subPath = Path.Combine(path, fileInfo.Name);
-
- foreach (var info in GetFileInfosRecursive(subPath))
- {
- yield return info;
- }
+ var subPath = Path.Combine(root, fileInfo.Name);
+ GetFileInfosRecursive(subPath, razorFiles);
}
else if (Path.GetExtension(fileInfo.Name)
.Equals(FileExtension, StringComparison.OrdinalIgnoreCase))
{
- var relativePath = Path.Combine(currentPath, fileInfo.Name);
+ var relativePath = Path.Combine(root, fileInfo.Name);
var info = new RelativeFileInfo(fileInfo, relativePath);
- yield return info;
+ razorFiles.Add(info);
}
}
}
- protected virtual RazorFileInfo ParseView([NotNull] RelativeFileInfo fileInfo,
- [NotNull] IBeforeCompileContext context)
+ protected virtual PrecompilationCacheEntry GetCacheEntry([NotNull] RelativeFileInfo fileInfo)
{
using (var stream = fileInfo.FileInfo.CreateReadStream())
{
- var results = _host.GenerateCode(fileInfo.RelativePath, stream);
+ var host = GetRazorHost();
+ var results = host.GenerateCode(fileInfo.RelativePath, stream);
- foreach (var parserError in results.ParserErrors)
+ if (results.Success)
{
- var diagnostic = parserError.ToDiagnostics(fileInfo.FileInfo.PhysicalPath);
- context.Diagnostics.Add(diagnostic);
- }
-
- var generatedCode = results.GeneratedCode;
-
- if (generatedCode != null)
- {
- var syntaxTree = SyntaxTreeGenerator.Generate(generatedCode,
+ var syntaxTree = SyntaxTreeGenerator.Generate(results.GeneratedCode,
fileInfo.FileInfo.PhysicalPath,
CompilationSettings);
- var fullTypeName = results.GetMainClassName(_host, syntaxTree);
+ var fullTypeName = results.GetMainClassName(host, syntaxTree);
if (fullTypeName != null)
{
- context.CSharpCompilation = context.CSharpCompilation.AddSyntaxTrees(syntaxTree);
-
var hash = RazorFileHash.GetHash(fileInfo.FileInfo);
-
- return new RazorFileInfo()
+ var razorFileInfo = new RazorFileInfo
{
- FullTypeName = fullTypeName,
RelativePath = fileInfo.RelativePath,
LastModified = fileInfo.FileInfo.LastModified,
Length = fileInfo.FileInfo.Length,
- Hash = hash,
+ FullTypeName = fullTypeName
};
+
+ return new PrecompilationCacheEntry(razorFileInfo, syntaxTree);
}
}
+ else
+ {
+ var diagnostics = results.ParserErrors
+ .Select(error => error.ToDiagnostics(fileInfo.FileInfo.PhysicalPath))
+ .ToList();
+
+ return new PrecompilationCacheEntry(diagnostics);
+ }
}
return null;
diff --git a/src/Microsoft.AspNet.Mvc.Razor/project.json b/src/Microsoft.AspNet.Mvc.Razor/project.json
index 8b0ebed627..75da75289d 100644
--- a/src/Microsoft.AspNet.Mvc.Razor/project.json
+++ b/src/Microsoft.AspNet.Mvc.Razor/project.json
@@ -9,7 +9,8 @@
"Microsoft.AspNet.Mvc.Core": "6.0.0-*",
"Microsoft.AspNet.Mvc.Razor.Host": "6.0.0-*",
"Microsoft.CodeAnalysis.CSharp": "1.0.0-rc1-*",
- "Microsoft.Framework.Runtime.Roslyn.Common": "1.0.0-*"
+ "Microsoft.Framework.Cache.Memory": "1.0.0-*",
+ "Microsoft.Framework.Runtime.Roslyn.Common": "1.0.0-*"
},
"frameworks": {
"aspnet50": {
@@ -21,6 +22,10 @@
"System.Threading.Tasks": ""
}
},
- "aspnetcore50": {}
+ "aspnetcore50": {
+ "dependencies": {
+ "System.Threading.Tasks.Parallel": "4.0.0-beta-*"
+ }
+ }
}
}
diff --git a/src/Microsoft.AspNet.Mvc/RazorPreCompileModule.cs b/src/Microsoft.AspNet.Mvc/RazorPreCompileModule.cs
index d749009194..351f3b6b18 100644
--- a/src/Microsoft.AspNet.Mvc/RazorPreCompileModule.cs
+++ b/src/Microsoft.AspNet.Mvc/RazorPreCompileModule.cs
@@ -9,6 +9,7 @@ using Microsoft.AspNet.Hosting;
using Microsoft.AspNet.Mvc.Razor;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.Framework.Cache.Memory;
using Microsoft.Framework.DependencyInjection;
using Microsoft.Framework.DependencyInjection.Fallback;
using Microsoft.Framework.DependencyInjection.ServiceLookup;
@@ -19,10 +20,15 @@ namespace Microsoft.AspNet.Mvc
public abstract class RazorPreCompileModule : ICompileModule
{
private readonly IServiceProvider _appServices;
+ private readonly IMemoryCache _memoryCache;
public RazorPreCompileModule(IServiceProvider services)
{
_appServices = services;
+ // When ListenForMemoryPressure is true, the MemoryCache evicts items at every gen2 collection.
+ // In DTH, gen2 happens frequently enough to make it undesirable for caching precompilation results. We'll
+ // disable listening for memory pressure for the MemoryCache instance used by precompilation.
+ _memoryCache = new MemoryCache(new MemoryCacheOptions { ListenForMemoryPressure = false });
}
protected virtual string FileExtension { get; } = ".cshtml";
@@ -39,7 +45,7 @@ namespace Microsoft.AspNet.Mvc
sc.AddMvc();
var serviceProvider = BuildFallbackServiceProvider(sc, _appServices);
- var viewCompiler = new RazorPreCompiler(serviceProvider, compilationSettings);
+ var viewCompiler = new RazorPreCompiler(serviceProvider, _memoryCache, compilationSettings);
viewCompiler.CompileViews(context);
}