Move caching of compilation results to its own layer.

This will allow only creating the razor compilation when really needed, with the right lifetime.
This commit is contained in:
YishaiGalatzer 2014-10-11 15:35:11 -07:00
parent 13ee27c92c
commit 75084ba0cd
8 changed files with 55 additions and 69 deletions

View File

@ -9,13 +9,13 @@ using System.Reflection;
namespace Microsoft.AspNet.Mvc.Razor
{
public class CompilerCache
public class CompilerCache : ICompilerCache
{
private readonly ConcurrentDictionary<string, CompilerCacheEntry> _cache;
private static readonly Type[] EmptyType = new Type[0];
public CompilerCache([NotNull] IEnumerable<Assembly> assemblies)
: this(GetFileInfos(assemblies))
public CompilerCache([NotNull] IAssemblyProvider provider)
: this(GetFileInfos(provider.CandidateAssemblies))
{
}

View File

@ -6,7 +6,7 @@ using System;
namespace Microsoft.AspNet.Mvc.Razor
{
/// <summary>
/// An entry in <see cref="CompilerCache"/> that contain metadata about precompiled and dynamically compiled file.
/// An entry in <see cref="ICompilerCache"/> that contain metadata about precompiled and dynamically compiled file.
/// </summary>
public class CompilerCacheEntry
{

View File

@ -0,0 +1,15 @@
// 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;
using Microsoft.AspNet.FileSystems;
namespace Microsoft.AspNet.Mvc.Razor
{
public interface ICompilerCache
{
CompilationResult GetOrAdd([NotNull] RelativeFileInfo fileInfo,
bool enableInstrumentation,
[NotNull] Func<CompilationResult> compile);
}
}

View File

@ -6,7 +6,7 @@ using System;
namespace Microsoft.AspNet.Mvc.Razor
{
/// <summary>
/// Represents the result of compilation that does not come from the <see cref="CompilerCache" />.
/// Represents the result of compilation that does not come from the <see cref="ICompilerCache" />.
/// </summary>
public class UncachedCompilationResult : CompilationResult
{

View File

@ -16,42 +16,18 @@ namespace Microsoft.AspNet.Mvc.Razor
/// </remarks>
public class RazorCompilationService : IRazorCompilationService
{
private readonly IServiceProvider _serviceProvider;
private readonly CompilerCache _cache;
private ICompilationService _compilationService;
private ICompilationService CompilationService
{
get
{
if (_compilationService == null)
{
_compilationService = _serviceProvider.GetService<ICompilationService>();
}
return _compilationService;
}
}
private readonly ICompilationService _compilationService;
private readonly IMvcRazorHost _razorHost;
public RazorCompilationService(IServiceProvider serviceProvider,
IAssemblyProvider _assemblyProvider,
public RazorCompilationService(ICompilationService compilationService,
IMvcRazorHost razorHost)
{
_serviceProvider = serviceProvider;
_compilationService = compilationService;
_razorHost = razorHost;
_cache = new CompilerCache(_assemblyProvider.CandidateAssemblies);
}
/// <inheritdoc />
public CompilationResult Compile([NotNull] RelativeFileInfo file, bool isInstrumented)
{
return _cache.GetOrAdd(file, isInstrumented, () => CompileCore(file, isInstrumented));
}
internal CompilationResult CompileCore(RelativeFileInfo file, bool isInstrumented)
{
_razorHost.EnableInstrumentation = isInstrumented;
@ -68,7 +44,7 @@ namespace Microsoft.AspNet.Mvc.Razor
return CompilationResult.Failed(file.FileInfo, results.GeneratedCode, messages);
}
return CompilationService.Compile(file.FileInfo, results.GeneratedCode);
return _compilationService.Compile(file.FileInfo, results.GeneratedCode);
}
}
}

View File

@ -12,30 +12,36 @@ namespace Microsoft.AspNet.Mvc.Razor
/// </summary>
public class VirtualPathRazorPageFactory : IRazorPageFactory
{
private IRazorCompilationService _compilationService;
private IRazorCompilationService CompilationService
{
get
{
if (_compilationService == null)
{
_compilationService = _serviceProvider.GetService<IRazorCompilationService>();
}
return _compilationService;
}
}
private readonly ITypeActivator _activator;
private readonly IServiceProvider _serviceProvider;
private readonly IFileInfoCache _fileInfoCache;
private readonly ICompilerCache _compilerCache;
private IRazorCompilationService _razorcompilationService;
private IRazorCompilationService RazorCompilationService
{
get
{
if (_razorcompilationService == null)
{
// it is ok to use the cached service provider because this service has
// a lifetime of Scoped.
_razorcompilationService = _serviceProvider.GetService<IRazorCompilationService>();
}
return _razorcompilationService;
}
}
public VirtualPathRazorPageFactory(ITypeActivator typeActivator,
IServiceProvider serviceProvider,
ICompilerCache compilerCache,
IFileInfoCache fileInfoCache)
{
_activator = typeActivator;
_serviceProvider = serviceProvider;
_compilerCache = compilerCache;
_fileInfoCache = fileInfoCache;
}
@ -58,7 +64,9 @@ namespace Microsoft.AspNet.Mvc.Razor
RelativePath = relativePath,
};
var result = CompilationService.Compile(relativeFileInfo, enableInstrumentation);
var result = _compilerCache.GetOrAdd(relativeFileInfo, enableInstrumentation, () =>
RazorCompilationService.Compile(relativeFileInfo, enableInstrumentation));
var page = (IRazorPage)_activator.CreateInstance(_serviceProvider, result.CompiledType);
page.Path = relativePath;

View File

@ -51,6 +51,7 @@ namespace Microsoft.AspNet.Mvc
// The host is designed to be discarded after consumption and is very inexpensive to initialize.
yield return describe.Transient<IMvcRazorHost, MvcRazorHost>();
yield return describe.Singleton<ICompilerCache, CompilerCache>();
yield return describe.Singleton<ICompilationService, RoslynCompilationService>();
yield return describe.Singleton<IRazorCompilationService, RazorCompilationService>();

View File

@ -19,7 +19,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Test
[Theory]
[InlineData(@"src\work\myapp", @"src\work\myapp\views\index\home.cshtml")]
[InlineData(@"src\work\myapp\", @"src\work\myapp\views\index\home.cshtml")]
public void CompileCoreCalculatesRootRelativePath(string appPath, string viewPath)
public void CompileCalculatesRootRelativePath(string appPath, string viewPath)
{
// Arrange
var host = new Mock<IMvcRazorHost>();
@ -27,11 +27,6 @@ namespace Microsoft.AspNet.Mvc.Razor.Test
.Returns(GetGeneratorResult())
.Verifiable();
var ap = new Mock<IAssemblyProvider>();
ap.SetupGet(e => e.CandidateAssemblies)
.Returns(Enumerable.Empty<Assembly>())
.Verifiable();
var fileInfo = new Mock<IFileInfo>();
fileInfo.Setup(f => f.PhysicalPath).Returns(viewPath);
fileInfo.Setup(f => f.CreateReadStream()).Returns(Stream.Null);
@ -40,11 +35,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Test
compiler.Setup(c => c.Compile(fileInfo.Object, It.IsAny<string>()))
.Returns(CompilationResult.Successful(typeof(RazorCompilationServiceTest)));
var serviceProvider = new Mock<IServiceProvider>();
serviceProvider.Setup(sp => sp.GetService(It.Is<Type>(t => t == typeof(ICompilationService))))
.Returns(compiler.Object);
var razorService = new RazorCompilationService(serviceProvider.Object, ap.Object, host.Object);
var razorService = new RazorCompilationService(compiler.Object, host.Object);
var relativeFileInfo = new RelativeFileInfo()
{
@ -53,7 +44,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Test
};
// Act
razorService.CompileCore(relativeFileInfo, isInstrumented: false);
razorService.Compile(relativeFileInfo, isInstrumented: false);
// Assert
host.Verify();
@ -62,25 +53,20 @@ namespace Microsoft.AspNet.Mvc.Razor.Test
[Theory]
[InlineData(false)]
[InlineData(true)]
public void CompileCoreSetsEnableInstrumentationOnHost(bool enableInstrumentation)
public void CompileSetsEnableInstrumentationOnHost(bool enableInstrumentation)
{
// Arrange
var host = new Mock<IMvcRazorHost>();
host.SetupAllProperties();
host.Setup(h => h.GenerateCode(It.IsAny<string>(), It.IsAny<Stream>()))
.Returns(GetGeneratorResult()); var assemblyProvider = new Mock<IAssemblyProvider>();
assemblyProvider.SetupGet(e => e.CandidateAssemblies)
.Returns(Enumerable.Empty<Assembly>());
.Returns(GetGeneratorResult());
var compiler = new Mock<ICompilationService>();
compiler.Setup(c => c.Compile(It.IsAny<IFileInfo>(), It.IsAny<string>()))
.Returns(CompilationResult.Successful(GetType()));
var serviceProvider = new Mock<IServiceProvider>();
serviceProvider.Setup(sp => sp.GetService(It.Is<Type>(t => t == typeof(ICompilationService))))
.Returns(compiler.Object);
var razorService = new RazorCompilationService(compiler.Object, host.Object);
var razorService = new RazorCompilationService(serviceProvider.Object, assemblyProvider.Object, host.Object);
var relativeFileInfo = new RelativeFileInfo()
{
FileInfo = Mock.Of<IFileInfo>(),
@ -88,7 +74,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Test
};
// Act
razorService.CompileCore(relativeFileInfo, isInstrumented: enableInstrumentation);
razorService.Compile(relativeFileInfo, isInstrumented: enableInstrumentation);
// Assert
Assert.Equal(enableInstrumentation, host.Object.EnableInstrumentation);