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:
parent
13ee27c92c
commit
75084ba0cd
|
|
@ -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))
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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>();
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Reference in New Issue