* Move precompiled assemblies into a resource in the primary assembly.

* Emit the primary assembly if a tag helper requires it.
* Make TagHelperSample.Web use precompilation.

Fixes #1693
This commit is contained in:
Pranav K 2015-02-02 07:57:23 -08:00
parent 6b3119e2c5
commit 57f5b19f25
25 changed files with 698 additions and 154 deletions

View File

@ -3,13 +3,19 @@
using System;
using Microsoft.AspNet.Mvc;
using Microsoft.Framework.Runtime;
namespace MvcSample.Web
{
public class RazorPreCompilation : RazorPreCompileModule
{
public RazorPreCompilation(IServiceProvider provider) : base(provider)
public RazorPreCompilation(IServiceProvider provider,
IApplicationEnvironment applicationEnvironment)
: base(provider)
{
GenerateSymbols = string.Equals(applicationEnvironment.Configuration,
"debug",
StringComparison.OrdinalIgnoreCase);
}
}
}

View File

@ -0,0 +1,21 @@
// 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.Mvc;
using Microsoft.Framework.Runtime;
namespace TagHelperSample.Web
{
public class TagHelperPrecompilation : RazorPreCompileModule
{
public TagHelperPrecompilation(IServiceProvider provider,
IApplicationEnvironment applicationEnvironment)
: base(provider)
{
GenerateSymbols = string.Equals(applicationEnvironment.Configuration,
"debug",
StringComparison.OrdinalIgnoreCase);
}
}
}

View File

@ -8,6 +8,7 @@ using System.Reflection;
using Microsoft.AspNet.FileProviders;
using Microsoft.Framework.Cache.Memory;
using Microsoft.Framework.OptionsModel;
using Microsoft.Framework.Runtime;
namespace Microsoft.AspNet.Mvc.Razor
{
@ -16,6 +17,7 @@ namespace Microsoft.AspNet.Mvc.Razor
/// </summary>
public class CompilerCache : ICompilerCache
{
private static readonly TypeInfo RazorFileInfoCollectionType = typeof(RazorFileInfoCollection).GetTypeInfo();
private readonly IFileProvider _fileProvider;
private readonly IMemoryCache _cache;
@ -23,27 +25,30 @@ namespace Microsoft.AspNet.Mvc.Razor
/// Initializes a new instance of <see cref="CompilerCache"/> populated with precompiled views
/// discovered using <paramref name="provider"/>.
/// </summary>
/// <param name="provider">
/// An <see cref="IAssemblyProvider"/> representing the assemblies
/// used to search for pre-compiled views.
/// </param>
/// <param name="assemblyProvider">The <see cref="IAssemblyProvider"/> that provides assemblies
/// for precompiled view discovery.</param>
/// <param name="loaderContextAccessor">The <see cref="IAssemblyLoadContextAccessor"/>.</param>
/// <param name="optionsAccessor">An accessor to the <see cref="RazorViewEngineOptions"/>.</param>
public CompilerCache(IAssemblyProvider provider,
public CompilerCache(IAssemblyProvider assemblyProvider,
IAssemblyLoadContextAccessor loadContextAccessor,
IOptions<RazorViewEngineOptions> optionsAccessor)
: this(GetFileInfos(provider.CandidateAssemblies), optionsAccessor.Options.FileProvider)
: this(GetFileInfos(assemblyProvider.CandidateAssemblies),
loadContextAccessor.GetLoadContext(RazorFileInfoCollectionType.Assembly),
optionsAccessor.Options.FileProvider)
{
}
// Internal for unit testing
internal CompilerCache(IEnumerable<RazorFileInfoCollection> razorFileInfoCollection,
internal CompilerCache(IEnumerable<RazorFileInfoCollection> razorFileInfoCollections,
IAssemblyLoadContext loadContext,
IFileProvider fileProvider)
{
_fileProvider = fileProvider;
_cache = new MemoryCache(new MemoryCacheOptions { ListenForMemoryPressure = false });
var cacheEntries = new List<CompilerCacheEntry>();
foreach (var viewCollection in razorFileInfoCollection)
foreach (var viewCollection in razorFileInfoCollections)
{
var containingAssembly = viewCollection.GetType().GetTypeInfo().Assembly;
var containingAssembly = viewCollection.LoadAssembly(loadContext);
foreach (var fileInfo in viewCollection.FileInfos)
{
var viewType = containingAssembly.GetType(fileInfo.FullTypeName);
@ -247,7 +252,7 @@ namespace Microsoft.AspNet.Mvc.Razor
.Select(c => (RazorFileInfoCollection)Activator.CreateInstance(c));
}
private static bool Match(Type t)
internal static bool Match(Type t)
{
var inAssemblyType = typeof(RazorFileInfoCollection);
if (inAssemblyType.IsAssignableFrom(t))
@ -262,7 +267,6 @@ namespace Microsoft.AspNet.Mvc.Razor
return false;
}
private class GetOrAddResult
{
public CompilerCacheEntry CompilerCacheEntry { get; set; }

View File

@ -9,8 +9,8 @@ using System.IO;
using System.Linq;
using System.Reflection;
using System.Reflection.PortableExecutable;
using System.Runtime.InteropServices;
using Microsoft.AspNet.FileProviders;
using Microsoft.AspNet.Mvc.Razor.Internal;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Emit;
@ -24,7 +24,7 @@ namespace Microsoft.AspNet.Mvc.Razor
/// </summary>
public class RoslynCompilationService : ICompilationService
{
private readonly Lazy<bool> _supportsPdbGeneration = new Lazy<bool>(SupportsPdbGeneration);
private readonly Lazy<bool> _supportsPdbGeneration = new Lazy<bool>(SymbolsUtility.SupportsSymbolsGeneration);
private readonly ConcurrentDictionary<string, AssemblyMetadata> _metadataFileCache =
new ConcurrentDictionary<string, AssemblyMetadata>(StringComparer.OrdinalIgnoreCase);
@ -229,32 +229,6 @@ namespace Microsoft.AspNet.Mvc.Razor
return diagnostic.IsWarningAsError || diagnostic.Severity == DiagnosticSeverity.Error;
}
private static bool SupportsPdbGeneration()
{
try
{
if (PlatformHelper.IsMono)
{
return false;
}
// Check for the pdb writer component that roslyn uses to generate pdbs
const string SymWriterGuid = "0AE2DEB0-F901-478b-BB9F-881EE8066788";
var type = Marshal.GetTypeFromCLSID(new Guid(SymWriterGuid));
if (type != null)
{
// This line will throw if pdb generation is not supported.
Activator.CreateInstance(type);
return true;
}
}
catch
{
}
return false;
}
}
}

View File

@ -0,0 +1,45 @@
// 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 System.Runtime.InteropServices;
namespace Microsoft.AspNet.Mvc.Razor.Internal
{
/// <summary>
/// Utility type for determining if a platform supports symbol file generation.
/// </summary>
public class SymbolsUtility
{
private const string SymWriterGuid = "0AE2DEB0-F901-478b-BB9F-881EE8066788";
/// <summary>
/// Determines if the current platform supports symbols (pdb) generation.
/// </summary>
/// <returns><c>true</c> if pdb generation is supported; <c>false</c> otherwise.</returns>
public static bool SupportsSymbolsGeneration()
{
if (PlatformHelper.IsMono)
{
return false;
}
try
{
// Check for the pdb writer component that roslyn uses to generate pdbs
var type = Marshal.GetTypeFromCLSID(new Guid(SymWriterGuid));
if (type != null)
{
// This line will throw if pdb generation is not supported.
Activator.CreateInstance(type);
return true;
}
}
catch
{
}
return false;
}
}
}

View File

@ -378,6 +378,22 @@ namespace Microsoft.AspNet.Mvc.Razor
return GetString("RazorHash_UnsupportedHashAlgorithm");
}
/// <summary>
/// The resource '{0}' specified by '{1}' could not be found.
/// </summary>
internal static string RazorFileInfoCollection_ResourceCouldNotBeFound
{
get { return GetString("RazorFileInfoCollection_ResourceCouldNotBeFound"); }
}
/// <summary>
/// The resource '{0}' specified by '{1}' could not be found.
/// </summary>
internal static string FormatRazorFileInfoCollection_ResourceCouldNotBeFound(object p0, object p1)
{
return string.Format(CultureInfo.CurrentCulture, GetString("RazorFileInfoCollection_ResourceCouldNotBeFound"), p0, p1);
}
private static string GetString(string name, params string[] formatterNames)
{
var value = _resourceManager.GetString(name);

View File

@ -0,0 +1,80 @@
// 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 System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading;
using Microsoft.AspNet.Razor.Runtime.TagHelpers;
using Microsoft.Framework.Runtime;
using Microsoft.Framework.Runtime.Roslyn;
namespace Microsoft.AspNet.Mvc.Razor
{
/// <summary>
/// <see cref="TagHelperDescriptorResolver"/> used during Razor precompilation.
/// </summary>
public class PrecompilationTagHelperTypeResolver : TagHelperTypeResolver
{
private static readonly string TagHelperTypeName = typeof(ITagHelper).FullName;
private readonly IBeforeCompileContext _compileContext;
private readonly IAssemblyLoadContext _loadContext;
private object _compilationLock = new object();
private bool _assemblyEmited;
private TypeInfo[] _exportedTypeInfos;
/// <summary>
/// Initializes a new instance of <see cref="PrecompilationTagHelperTypeResolver"/>.
/// </summary>
/// <param name="compileContext">The <see cref="IBeforeCompileContext"/>.</param>
/// <param name="loadContext">The <see cref="IAssemblyLoadContext"/>.</param>
public PrecompilationTagHelperTypeResolver([NotNull] IBeforeCompileContext compileContext,
[NotNull] IAssemblyLoadContext loadContext)
{
_compileContext = compileContext;
_loadContext = loadContext;
}
/// <inheritdoc />
protected override IEnumerable<TypeInfo> GetExportedTypes([NotNull] AssemblyName assemblyName)
{
var compilingAssemblyName = _compileContext.Compilation.AssemblyName;
if (string.Equals(assemblyName.Name, compilingAssemblyName, StringComparison.Ordinal))
{
return LazyInitializer.EnsureInitialized(ref _exportedTypeInfos,
ref _assemblyEmited,
ref _compilationLock,
GetExportedTypesFromCompilation);
}
return base.GetExportedTypes(assemblyName);
}
private TypeInfo[] GetExportedTypesFromCompilation()
{
using (var stream = new MemoryStream())
{
var assemblyName = string.Join(".", _compileContext.Compilation.AssemblyName,
nameof(PrecompilationTagHelperTypeResolver),
Path.GetRandomFileName());
var emitResult = _compileContext.Compilation
.WithAssemblyName(assemblyName)
.Emit(stream);
if (!emitResult.Success)
{
// Return an empty sequence. Compilation will fail once precompilation completes.
return new TypeInfo[0];
}
stream.Position = 0;
var assembly = _loadContext.LoadStream(stream, assemblySymbols: null);
return assembly.ExportedTypes
.Select(type => type.GetTypeInfo())
.ToArray();
}
}
}
}

View File

@ -1,12 +1,63 @@
// 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 System.Collections.Generic;
using System.IO;
using System.Reflection;
using Microsoft.Framework.Runtime;
namespace Microsoft.AspNet.Mvc.Razor
{
/// <summary>
/// Specifies metadata about precompiled views.
/// </summary>
public abstract class RazorFileInfoCollection
{
/// <summary>
/// Gets or sets the name of the resource containing the precompiled binary.
/// </summary>
public string AssemblyResourceName { get; protected set; }
/// <summary>
/// Gets or sets the name of the resource that contains the symbols (pdb).
/// </summary>
public string SymbolsResourceName { get; protected set; }
/// <summary>
/// Gets the <see cref="IReadOnlyList{T}{T}"/> of <see cref="RazorFileInfo"/>s.
/// </summary>
public IReadOnlyList<RazorFileInfo> FileInfos { get; protected set; }
/// <summary>
/// Loads the assembly containing precompiled views.
/// </summary>
/// <param name="loadContext">The <see cref="IAssemblyLoadContext"/>.</param>
/// <returns>The <see cref="Assembly"/> containing precompiled views.</returns>
public virtual Assembly LoadAssembly(IAssemblyLoadContext loadContext)
{
var viewCollectionAssembly = GetType().GetTypeInfo().Assembly;
using (var assemblyStream = viewCollectionAssembly.GetManifestResourceStream(AssemblyResourceName))
{
if (assemblyStream == null)
{
var message = Resources.FormatRazorFileInfoCollection_ResourceCouldNotBeFound(AssemblyResourceName,
GetType().FullName);
throw new InvalidOperationException(message);
}
Stream symbolsStream = null;
if (!string.IsNullOrEmpty(SymbolsResourceName))
{
symbolsStream = viewCollectionAssembly.GetManifestResourceStream(SymbolsResourceName);
}
using (symbolsStream)
{
return loadContext.LoadStream(assemblyStream, symbolsStream);
}
}
}
}
}

View File

@ -1,9 +1,10 @@
// 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 System.Globalization;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.Framework.Runtime;
using Microsoft.Framework.Runtime.Roslyn;
namespace Microsoft.AspNet.Mvc.Razor
@ -12,23 +13,26 @@ namespace Microsoft.AspNet.Mvc.Razor
{
private string _fileFormat;
public RazorFileInfoCollectionGenerator([NotNull] IEnumerable<RazorFileInfo> fileInfos,
public RazorFileInfoCollectionGenerator([NotNull] RazorFileInfoCollection fileInfoCollection,
[NotNull] CompilationSettings compilationSettings)
{
FileInfos = fileInfos;
RazorFileInfoCollection = fileInfoCollection;
CompilationSettings = compilationSettings;
}
protected IEnumerable<RazorFileInfo> FileInfos { get; }
protected RazorFileInfoCollection RazorFileInfoCollection { get; }
protected CompilationSettings CompilationSettings { get; }
public virtual SyntaxTree GenerateCollection()
{
var builder = new StringBuilder();
builder.Append(Top);
builder.AppendFormat(CultureInfo.InvariantCulture,
TopFormat,
RazorFileInfoCollection.AssemblyResourceName,
RazorFileInfoCollection.SymbolsResourceName);
foreach (var fileInfo in FileInfos)
foreach (var fileInfo in RazorFileInfoCollection.FileInfos)
{
var perFileEntry = GenerateFile(fileInfo);
builder.Append(perFileEntry);
@ -55,25 +59,27 @@ namespace Microsoft.AspNet.Mvc.Razor
fileInfo.HashAlgorithmVersion);
}
protected virtual string Top
protected virtual string TopFormat
{
get
{
return
@"using System;
$@"using System;
using System.Collections.Generic;
using Microsoft.AspNet.Mvc.Razor;
using System.Reflection;
using {typeof(RazorFileInfoCollection).Namespace};
namespace __ASP_ASSEMBLY
{
public class __PreGeneratedViewCollection : " + nameof(RazorFileInfoCollection) + @"
{
{{{{
public class __PreGeneratedViewCollection : {nameof(RazorFileInfoCollection)}
{{{{
public __PreGeneratedViewCollection()
{
var fileInfos = new List<" + nameof(RazorFileInfo) + @">();
" + nameof(RazorFileInfoCollection.FileInfos) + @" = fileInfos;
" + nameof(RazorFileInfo) + @" info;
{{{{
{nameof(RazorFileInfoCollection.AssemblyResourceName)} = ""{{0}}"";
{nameof(RazorFileInfoCollection.SymbolsResourceName)} = ""{{1}}"";
var fileInfos = new List<{nameof(RazorFileInfo)}>();
{nameof(RazorFileInfoCollection.FileInfos)} = fileInfos;
{nameof(RazorFileInfo)} info;
";
}
}
@ -83,9 +89,20 @@ namespace __ASP_ASSEMBLY
get
{
return
@" }
}
}
$@"
}}
private static Assembly _loadedAssembly;
public override Assembly LoadAssembly({typeof(IAssemblyLoadContext).FullName} loadContext)
{{
if (_loadedAssembly == null)
{{
_loadedAssembly = base.LoadAssembly(loadContext);
}}
return _loadedAssembly;
}}
}}
}}
";
}
}
@ -97,16 +114,16 @@ namespace __ASP_ASSEMBLY
if (_fileFormat == null)
{
_fileFormat =
" info = new "
+ nameof(RazorFileInfo) + @"
{{
" + nameof(RazorFileInfo.LastModified) + @" = DateTime.FromFileTimeUtc({0:D}).ToLocalTime(),
" + nameof(RazorFileInfo.Length) + @" = {1:D},
" + nameof(RazorFileInfo.RelativePath) + @" = @""{2}"",
" + nameof(RazorFileInfo.FullTypeName) + @" = @""{3}"",
" + nameof(RazorFileInfo.Hash) + @" = ""{4}"",
" + nameof(RazorFileInfo.HashAlgorithmVersion) + @" = {5},
}};
$@"
info = new {nameof(RazorFileInfo)}
{{{{
{nameof(RazorFileInfo.LastModified)} = DateTime.FromFileTimeUtc({{0:D}}).ToLocalTime(),
{nameof(RazorFileInfo.Length)} = {{1:D}},
{nameof(RazorFileInfo.RelativePath)} = @""{{2}}"",
{nameof(RazorFileInfo.FullTypeName)} = @""{{3}}"",
{nameof(RazorFileInfo.Hash)} = ""{{4}}"",
{nameof(RazorFileInfo.HashAlgorithmVersion)} = {{5}},
}}}};
fileInfos.Add(info);
";
}

View File

@ -5,12 +5,18 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using Microsoft.AspNet.FileProviders;
using Microsoft.AspNet.Mvc.Razor.Directives;
using Microsoft.AspNet.Mvc.Razor.Internal;
using Microsoft.AspNet.Razor.Runtime.TagHelpers;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.Framework.Cache.Memory;
using Microsoft.Framework.DependencyInjection;
using Microsoft.Framework.OptionsModel;
using Microsoft.Framework.Runtime;
using Microsoft.Framework.Runtime.Roslyn;
namespace Microsoft.AspNet.Mvc.Razor
@ -21,9 +27,12 @@ namespace Microsoft.AspNet.Mvc.Razor
private readonly IFileProvider _fileProvider;
public RazorPreCompiler([NotNull] IServiceProvider designTimeServiceProvider,
[NotNull] IBeforeCompileContext compileContext,
[NotNull] IMemoryCache precompilationCache,
[NotNull] CompilationSettings compilationSettings) :
this(designTimeServiceProvider,
compileContext,
designTimeServiceProvider.GetRequiredService<IAssemblyLoadContextAccessor>(),
designTimeServiceProvider.GetRequiredService<IOptions<RazorViewEngineOptions>>(),
precompilationCache,
compilationSettings)
@ -31,16 +40,30 @@ namespace Microsoft.AspNet.Mvc.Razor
}
public RazorPreCompiler([NotNull] IServiceProvider designTimeServiceProvider,
[NotNull] IBeforeCompileContext compileContext,
[NotNull] IAssemblyLoadContextAccessor loadContextAccessor,
[NotNull] IOptions<RazorViewEngineOptions> optionsAccessor,
[NotNull] IMemoryCache precompilationCache,
[NotNull] CompilationSettings compilationSettings)
{
_serviceProvider = designTimeServiceProvider;
CompileContext = compileContext;
LoadContext = loadContextAccessor.GetLoadContext(GetType().GetTypeInfo().Assembly);
_fileProvider = optionsAccessor.Options.FileProvider;
CompilationSettings = compilationSettings;
PreCompilationCache = precompilationCache;
TagHelperTypeResolver = new PrecompilationTagHelperTypeResolver(CompileContext, LoadContext);
}
/// <summary>
/// Gets or sets a value that determines if symbols (.pdb) file for the precompiled views.
/// </summary>
public bool GenerateSymbols { get; set; }
protected IBeforeCompileContext CompileContext { get; }
protected IAssemblyLoadContext LoadContext { get; }
protected CompilationSettings CompilationSettings { get; }
protected IMemoryCache PreCompilationCache { get; }
@ -49,32 +72,37 @@ namespace Microsoft.AspNet.Mvc.Razor
protected virtual int MaxDegreesOfParallelism { get; } = Environment.ProcessorCount;
protected virtual TagHelperTypeResolver TagHelperTypeResolver { get; }
public virtual void CompileViews([NotNull] IBeforeCompileContext context)
public virtual void CompileViews()
{
var descriptors = CreateCompilationDescriptors(context);
var result = CreateFileInfoCollection();
if (descriptors.Any())
if (result != null)
{
var collectionGenerator = new RazorFileInfoCollectionGenerator(
descriptors,
result,
CompilationSettings);
var tree = collectionGenerator.GenerateCollection();
context.Compilation = context.Compilation.AddSyntaxTrees(tree);
CompileContext.Compilation = CompileContext.Compilation.AddSyntaxTrees(tree);
}
}
protected virtual IEnumerable<RazorFileInfo> CreateCompilationDescriptors(
[NotNull] IBeforeCompileContext context)
protected virtual RazorFileInfoCollection CreateFileInfoCollection()
{
var filesToProcess = new List<RelativeFileInfo>();
GetFileInfosRecursive(root: string.Empty, razorFiles: filesToProcess);
if (filesToProcess.Count == 0)
{
return null;
}
var razorFiles = new RazorFileInfo[filesToProcess.Count];
var syntaxTrees = new SyntaxTree[filesToProcess.Count];
var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = MaxDegreesOfParallelism };
var diagnosticsLock = new object();
var hasErrors = false;
Parallel.For(0, filesToProcess.Count, parallelOptions, index =>
{
@ -91,25 +119,91 @@ namespace Microsoft.AspNet.Mvc.Razor
}
else
{
hasErrors = true;
lock (diagnosticsLock)
{
foreach (var diagnostic in cacheEntry.Diagnostics)
{
context.Diagnostics.Add(diagnostic);
}
AddRange(CompileContext.Diagnostics, cacheEntry.Diagnostics);
}
}
}
});
context.Compilation = context.Compilation
.AddSyntaxTrees(syntaxTrees.Where(tree => tree != null));
return razorFiles.Where(file => file != null);
if (hasErrors)
{
// If any of the Razor files had syntax errors, don't emit the precompiled views assembly.
return null;
}
return GeneratePrecompiledAssembly(syntaxTrees.Where(tree => tree != null),
razorFiles.Where(file => file != null));
}
protected virtual RazorFileInfoCollection GeneratePrecompiledAssembly(
[NotNull] IEnumerable<SyntaxTree> syntaxTrees,
[NotNull] IEnumerable<RazorFileInfo> razorFileInfos)
{
var resourcePrefix = string.Join(".", CompileContext.Compilation.AssemblyName,
nameof(RazorPreCompiler),
Path.GetRandomFileName());
var assemblyResourceName = resourcePrefix + ".dll";
var applicationReference = CompileContext.Compilation.ToMetadataReference();
var references = CompileContext.Compilation.References
.Concat(new[] { applicationReference });
var preCompilationOptions = CompilationSettings.CompilationOptions
.WithOutputKind(OutputKind.DynamicallyLinkedLibrary);
var compilation = CSharpCompilation.Create(assemblyResourceName,
options: preCompilationOptions,
syntaxTrees: syntaxTrees,
references: references);
var generateSymbols = GenerateSymbols && SymbolsUtility.SupportsSymbolsGeneration();
// These streams are returned to the runtime and consequently cannot be disposed.
var assemblyStream = new MemoryStream();
var pdbStream = generateSymbols ? new MemoryStream() : null;
var emitResult = compilation.Emit(assemblyStream, pdbStream);
if (!emitResult.Success)
{
AddRange(CompileContext.Diagnostics, emitResult.Diagnostics);
return null;
}
else
{
assemblyStream.Position = 0;
var assemblyResource = new ResourceDescription(assemblyResourceName,
() => assemblyStream,
isPublic: true);
CompileContext.Resources.Add(assemblyResource);
string symbolsResourceName = null;
if (pdbStream != null)
{
symbolsResourceName = resourcePrefix + ".pdb";
pdbStream.Position = 0;
var pdbResource = new ResourceDescription(symbolsResourceName,
() => pdbStream,
isPublic: true);
CompileContext.Resources.Add(pdbResource);
}
return new PrecompileRazorFileInfoCollection(assemblyResourceName,
symbolsResourceName,
razorFileInfos.ToList());
}
}
protected IMvcRazorHost GetRazorHost()
{
return _serviceProvider.GetRequiredService<IMvcRazorHost>();
var descriptorResolver = new TagHelperDescriptorResolver(TagHelperTypeResolver);
return new MvcRazorHost(new DefaultCodeTreeCache(_fileProvider))
{
TagHelperDescriptorResolver = descriptorResolver
};
}
private PrecompilationCacheEntry OnCacheMiss(ICacheSetContext cacheSetContext)
@ -193,5 +287,25 @@ namespace Microsoft.AspNet.Mvc.Razor
return null;
}
private static void AddRange<TVal>(IList<TVal> target, IEnumerable<TVal> source)
{
foreach (var diagnostic in source)
{
target.Add(diagnostic);
}
}
private class PrecompileRazorFileInfoCollection : RazorFileInfoCollection
{
public PrecompileRazorFileInfoCollection(string assemblyResourceName,
string symbolsResourceName,
IReadOnlyList<RazorFileInfo> fileInfos)
{
AssemblyResourceName = assemblyResourceName;
SymbolsResourceName = symbolsResourceName;
FileInfos = fileInfos;
}
}
}
}

View File

@ -186,4 +186,7 @@
<data name="RazorHash_UnsupportedHashAlgorithm" xml:space="preserve">
<value>Unsupported hash algorithm.</value>
</data>
<data name="RazorFileInfoCollection_ResourceCouldNotBeFound" xml:space="preserve">
<value>The resource '{0}' specified by '{1}' could not be found.</value>
</data>
</root>

View File

@ -124,7 +124,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>();
// Caches compilation artifacts across the lifetime of the application.
yield return describe.Singleton<ICompilerCache, CompilerCache>();

View File

@ -31,6 +31,11 @@ namespace Microsoft.AspNet.Mvc
_memoryCache = new MemoryCache(new MemoryCacheOptions { ListenForMemoryPressure = false });
}
/// <summary>
/// Gets or sets a value that determines if symbols (.pdb) file for the precompiled views.
/// </summary>
public bool GenerateSymbols { get; protected set; }
protected virtual string FileExtension { get; } = ".cshtml";
public virtual void BeforeCompile(IBeforeCompileContext context)
@ -45,8 +50,11 @@ namespace Microsoft.AspNet.Mvc
sc.AddMvc();
var serviceProvider = BuildFallbackServiceProvider(sc, _appServices);
var viewCompiler = new RazorPreCompiler(serviceProvider, _memoryCache, compilationSettings);
viewCompiler.CompileViews(context);
var viewCompiler = new RazorPreCompiler(serviceProvider, context, _memoryCache, compilationSettings)
{
GenerateSymbols = GenerateSymbols
};
viewCompiler.CompileViews();
}
public void AfterCompile(IAfterCompileContext context)

View File

@ -8,6 +8,7 @@ using System.Net;
using System.Reflection;
using System.Threading.Tasks;
using Microsoft.AspNet.Builder;
using Microsoft.AspNet.Mvc.Razor;
using Microsoft.AspNet.TestHost;
using Microsoft.Framework.DependencyInjection;
using Microsoft.Framework.Runtime;
@ -39,7 +40,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
// We will render a view that writes the fully qualified name of the Assembly containing the type of
// the view. If the view is precompiled, this assembly will be PrecompilationWebsite.
var assemblyName = typeof(Startup).GetTypeInfo().Assembly.GetName().ToString();
var assemblyNamePrefix = GetAssemblyNamePrefix();
try
{
@ -50,9 +51,9 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
// Assert - 1
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var parsedResponse1 = new ParsedResponse(responseContent);
Assert.Equal(assemblyName, parsedResponse1.ViewStart);
Assert.Equal(assemblyName, parsedResponse1.Layout);
Assert.Equal(assemblyName, parsedResponse1.Index);
Assert.StartsWith(assemblyNamePrefix, parsedResponse1.ViewStart);
Assert.StartsWith(assemblyNamePrefix, parsedResponse1.Layout);
Assert.StartsWith(assemblyNamePrefix, parsedResponse1.Index);
// Act - 2
// Touch the Layout file and verify it is now dynamically compiled.
@ -61,9 +62,9 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
// Assert - 2
var response2 = new ParsedResponse(responseContent);
Assert.NotEqual(assemblyName, response2.Layout);
Assert.Equal(assemblyName, response2.ViewStart);
Assert.Equal(assemblyName, response2.Index);
Assert.StartsWith(assemblyNamePrefix, response2.ViewStart);
Assert.StartsWith(assemblyNamePrefix, response2.Index);
Assert.DoesNotContain(assemblyNamePrefix, response2.Layout);
// Act - 3
// Touch the _ViewStart file and verify it is is dynamically compiled.
@ -72,8 +73,8 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
// Assert - 3
var response3 = new ParsedResponse(responseContent);
Assert.NotEqual(assemblyName, response3.ViewStart);
Assert.Equal(assemblyName, response3.Index);
Assert.NotEqual(assemblyNamePrefix, response3.ViewStart);
Assert.Equal(response2.Index, response3.Index);
Assert.Equal(response2.Layout, response3.Layout);
// Act - 4
@ -159,13 +160,13 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
public async Task PrecompiledView_UsesCompilationOptionsFromApplication()
{
// Arrange
var assemblyName = typeof(Startup).GetTypeInfo().Assembly.GetName().ToString();
var assemblyNamePrefix = GetAssemblyNamePrefix();
#if ASPNET50
var expected =
@"Value set inside ASPNET50 " + assemblyName;
@"Value set inside ASPNET50 " + assemblyNamePrefix;
#elif ASPNETCORE50
var expected =
@"Value set inside ASPNETCORE50 " + assemblyName;
@"Value set inside ASPNETCORE50 " + assemblyNamePrefix;
#endif
var server = TestServer.Create(_services, _app);
@ -176,14 +177,14 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
var responseContent = await response.Content.ReadAsStringAsync();
// Assert
Assert.Equal(expected, responseContent.Trim());
Assert.StartsWith(expected, responseContent.Trim());
}
[Fact]
public async Task DeletingPrecompiledGlobalFile_PriorToFirstRequestToAView_CausesViewToBeRecompiled()
{
// Arrange
var expected = typeof(Startup).GetTypeInfo().Assembly.GetName().ToString();
var expected = GetAssemblyNamePrefix();
var server = TestServer.Create(_services, _app);
var client = server.CreateClient();
@ -209,7 +210,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
var response2 = await client.GetStringAsync("http://localhost/Home/GlobalDeletedPriorToFirstRequest");
// Assert - 2
Assert.NotEqual(expected, response2.Trim());
Assert.DoesNotContain(expected, response2.Trim());
}
finally
{
@ -217,6 +218,49 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
}
}
[Fact]
public async Task TagHelpersFromTheApplication_CanBeAdded()
{
// Arrange
var assemblyNamePrefix = GetAssemblyNamePrefix();
var expected = @"<root data-root=""true""><input class=""form-control"" type=""number"" data-val=""true""" +
@" data-val-range=""The field Age must be between 10 and 100."" data-val-range-max=""100"" "+
@"data-val-range-min=""10"" id=""Age"" name=""Age"" value="""" /><a href="""">Back to List</a></root>";
var server = TestServer.Create(_services, _app);
var client = server.CreateClient();
// Act
var response = await client.GetStringAsync("http://localhost/TagHelpers/Add");
// Assert
var responseLines = response.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries);
Assert.StartsWith(assemblyNamePrefix, responseLines[0]);
Assert.Equal(expected, responseLines[1]);
}
[Fact]
public async Task TagHelpersFromTheApplication_CanBeRemoved()
{
// Arrange
var assemblyNamePrefix = GetAssemblyNamePrefix();
var expected = @"<root>root-content</root>";
var server = TestServer.Create(_services, _app);
var client = server.CreateClient();
// Act
var response = await client.GetStringAsync("http://localhost/TagHelpers/Remove");
// Assert
var responseLines = response.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries);
Assert.StartsWith(assemblyNamePrefix, responseLines[0]);
Assert.Equal(expected, responseLines[1]);
}
private static string GetAssemblyNamePrefix()
{
return typeof(Startup).GetTypeInfo().Assembly.GetName().Name + "." + nameof(RazorPreCompiler) + ".";
}
private static async Task<string> TouchFile(string viewsDir, string file)
{
var path = Path.Combine(viewsDir, file);

View File

@ -6,7 +6,9 @@ using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using Microsoft.Framework.Runtime;
using Moq;
using Xunit;
@ -15,13 +17,14 @@ namespace Microsoft.AspNet.Mvc.Razor
public class CompilerCacheTest
{
private const string ViewPath = "view-path";
private readonly IAssemblyLoadContext TestLoadContext = Mock.Of<IAssemblyLoadContext>();
[Fact]
public void GetOrAdd_ReturnsFileNotFoundResult_IfFileIsNotFoundInFileSystem()
{
// Arrange
var fileProvider = new TestFileProvider();
var cache = new CompilerCache(Enumerable.Empty<RazorFileInfoCollection>(), fileProvider);
var cache = new CompilerCache(Enumerable.Empty<RazorFileInfoCollection>(), TestLoadContext, fileProvider);
var type = GetType();
// Act
@ -38,7 +41,7 @@ namespace Microsoft.AspNet.Mvc.Razor
// Arrange
var fileProvider = new TestFileProvider();
fileProvider.AddFile(ViewPath, "some content");
var cache = new CompilerCache(Enumerable.Empty<RazorFileInfoCollection>(), fileProvider);
var cache = new CompilerCache(Enumerable.Empty<RazorFileInfoCollection>(), TestLoadContext, fileProvider);
var type = GetType();
var expected = UncachedCompilationResult.Successful(type, "hello world");
@ -60,7 +63,7 @@ namespace Microsoft.AspNet.Mvc.Razor
// Arrange
var fileProvider = new TestFileProvider();
fileProvider.AddFile(ViewPath, "some content");
var cache = new CompilerCache(Enumerable.Empty<RazorFileInfoCollection>(), fileProvider);
var cache = new CompilerCache(Enumerable.Empty<RazorFileInfoCollection>(), TestLoadContext, fileProvider);
var type = typeof(RuntimeCompileIdentical);
var expected = UncachedCompilationResult.Successful(type, "hello world");
@ -88,7 +91,7 @@ namespace Microsoft.AspNet.Mvc.Razor
// Arrange
var fileProvider = new TestFileProvider();
fileProvider.AddFile(ViewPath, "some content");
var cache = new CompilerCache(Enumerable.Empty<RazorFileInfoCollection>(), fileProvider);
var cache = new CompilerCache(Enumerable.Empty<RazorFileInfoCollection>(), TestLoadContext, fileProvider);
var type = typeof(RuntimeCompileIdentical);
var expected1 = UncachedCompilationResult.Successful(type, "hello world");
var expected2 = UncachedCompilationResult.Successful(type, "different content");
@ -116,7 +119,7 @@ namespace Microsoft.AspNet.Mvc.Razor
var mockFileProvider = new Mock<TestFileProvider> { CallBase = true };
var fileProvider = mockFileProvider.Object;
fileProvider.AddFile(ViewPath, "some content");
var cache = new CompilerCache(Enumerable.Empty<RazorFileInfoCollection>(), fileProvider);
var cache = new CompilerCache(Enumerable.Empty<RazorFileInfoCollection>(), TestLoadContext, fileProvider);
var type = typeof(RuntimeCompileIdentical);
var expected = UncachedCompilationResult.Successful(type, "hello world");
@ -168,34 +171,6 @@ namespace Microsoft.AspNet.Mvc.Razor
}
}
private class ViewCollection : RazorFileInfoCollection
{
private readonly List<RazorFileInfo> _fileInfos = new List<RazorFileInfo>();
public ViewCollection()
{
FileInfos = _fileInfos;
var content = new PreCompile().Content;
var length = Encoding.UTF8.GetByteCount(content);
Add(new RazorFileInfo()
{
FullTypeName = typeof(PreCompile).FullName,
Hash = Crc32.Calculate(GetMemoryStream(content)).ToString(CultureInfo.InvariantCulture),
HashAlgorithmVersion = 1,
LastModified = DateTime.FromFileTimeUtc(10000),
Length = length,
RelativePath = ViewPath,
});
}
public void Add(RazorFileInfo fileInfo)
{
_fileInfos.Add(fileInfo);
}
}
private static Stream GetMemoryStream(string content)
{
var bytes = Encoding.UTF8.GetBytes(content);
@ -211,10 +186,8 @@ namespace Microsoft.AspNet.Mvc.Razor
// Arrange
var instance = new RuntimeCompileIdentical();
var length = Encoding.UTF8.GetByteCount(instance.Content);
var collection = new ViewCollection();
var fileProvider = new TestFileProvider();
var cache = new CompilerCache(new[] { new ViewCollection() }, fileProvider);
var cache = new CompilerCache(new[] { new ViewCollection() }, TestLoadContext, fileProvider);
var fileInfo = new TestFileInfo
{
Length = length,
@ -222,7 +195,6 @@ namespace Microsoft.AspNet.Mvc.Razor
Content = instance.Content
};
fileProvider.AddFile(ViewPath, fileInfo);
var precompiledContent = new PreCompile().Content;
// Act
var result = cache.GetOrAdd(ViewPath,
@ -246,9 +218,8 @@ namespace Microsoft.AspNet.Mvc.Razor
// Arrange
var instance = (View)Activator.CreateInstance(resultViewType);
var length = Encoding.UTF8.GetByteCount(instance.Content);
var collection = new ViewCollection();
var fileProvider = new TestFileProvider();
var cache = new CompilerCache(new[] { new ViewCollection() }, fileProvider);
var cache = new CompilerCache(new[] { new ViewCollection() }, TestLoadContext, fileProvider);
var fileInfo = new TestFileInfo
{
@ -303,10 +274,9 @@ namespace Microsoft.AspNet.Mvc.Razor
RelativePath = "_GlobalImport.cshtml",
FullTypeName = typeof(RuntimeCompileIdentical).FullName
};
var precompiledViews = new ViewCollection();
precompiledViews.Add(globalRazorFileInfo);
var cache = new CompilerCache(new[] { precompiledViews }, fileProvider);
var cache = new CompilerCache(new[] { precompiledViews }, TestLoadContext, fileProvider);
// Act
var result = cache.GetOrAdd(ViewPath,
@ -325,8 +295,7 @@ namespace Microsoft.AspNet.Mvc.Razor
// Arrange
var precompiledViews = new ViewCollection();
var fileProvider = new TestFileProvider();
var precompiledView = precompiledViews.FileInfos[0];
var cache = new CompilerCache(new[] { precompiledViews }, fileProvider);
var cache = new CompilerCache(new[] { precompiledViews }, TestLoadContext, fileProvider);
// Act
var result = cache.GetOrAdd(ViewPath,
@ -351,7 +320,7 @@ namespace Microsoft.AspNet.Mvc.Razor
LastModified = precompiledView.LastModified,
};
fileProvider.AddFile(ViewPath, fileInfo);
var cache = new CompilerCache(new[] { precompiledViews }, fileProvider);
var cache = new CompilerCache(new[] { precompiledViews }, TestLoadContext, fileProvider);
// Act 1
var result1 = cache.GetOrAdd(ViewPath,
@ -381,12 +350,11 @@ namespace Microsoft.AspNet.Mvc.Razor
{
// Arrange
var expectedType = typeof(RuntimeCompileDifferent);
var lastModified = DateTime.UtcNow;
var fileProvider = new TestFileProvider();
var collection = new ViewCollection();
var precompiledFile = collection.FileInfos[0];
precompiledFile.RelativePath = "Views\\home\\index.cshtml";
var cache = new CompilerCache(new[] { collection }, fileProvider);
var cache = new CompilerCache(new[] { collection }, TestLoadContext, fileProvider);
var testFile = new TestFileInfo
{
Content = new PreCompile().Content,
@ -456,7 +424,7 @@ namespace Microsoft.AspNet.Mvc.Razor
fileProvider.AddFile(globalFileInfo.PhysicalPath, globalFileInfo);
viewCollection.Add(globalFile);
var cache = new CompilerCache(new[] { viewCollection }, fileProvider);
var cache = new CompilerCache(new[] { viewCollection }, TestLoadContext, fileProvider);
// Act 1
var result1 = cache.GetOrAdd(viewFileInfo.PhysicalPath,
@ -542,7 +510,7 @@ namespace Microsoft.AspNet.Mvc.Razor
fileProvider.AddFile(fileInfo.PhysicalPath, fileInfo);
fileProvider.AddFile(viewStartRazorFileInfo.RelativePath, globalFileInfo);
var viewCollection = new ViewCollection();
var cache = new CompilerCache(new[] { viewCollection }, fileProvider);
var cache = new CompilerCache(new[] { viewCollection }, TestLoadContext, fileProvider);
// Act
var result = cache.GetOrAdd(fileInfo.PhysicalPath,
@ -561,7 +529,7 @@ namespace Microsoft.AspNet.Mvc.Razor
// Arrange
var lastModified = DateTime.UtcNow;
var fileProvider = new TestFileProvider();
var cache = new CompilerCache(Enumerable.Empty<RazorFileInfoCollection>(), fileProvider);
var cache = new CompilerCache(Enumerable.Empty<RazorFileInfoCollection>(), TestLoadContext, fileProvider);
var fileInfo = new TestFileInfo
{
PhysicalPath = "test",
@ -592,5 +560,125 @@ namespace Microsoft.AspNet.Mvc.Razor
Assert.Null(actual2.CompiledContent);
Assert.Same(type, actual2.CompiledType);
}
[Fact]
public void Match_ReturnsFalse_IfTypeIsAbstract()
{
// Arrange
var type = typeof(AbstractRazorFileInfoCollection);
// Act
var result = CompilerCache.Match(type);
// Assert
Assert.False(result);
}
[Fact]
public void Match_ReturnsFalse_IfTypeHasGenericParameters()
{
// Arrange
var type = typeof(GenericRazorFileInfoCollection<>);
// Act
var result = CompilerCache.Match(type);
// Assert
Assert.False(result);
}
[Fact]
public void Match_ReturnsFalse_IfTypeDoesNotHaveDefaultConstructor()
{
// Arrange
var type = typeof(ParameterConstructorRazorFileInfoCollection);
// Act
var result = CompilerCache.Match(type);
// Assert
Assert.False(result);
}
[Fact]
public void Match_ReturnsFalse_IfTypeDoesNotDeriveFromRazorFileInfoCollection()
{
// Arrange
var type = typeof(NonSubTypeRazorFileInfoCollection);
// Act
var result = CompilerCache.Match(type);
// Assert
Assert.False(result);
}
[Fact]
public void Match_ReturnsTrue_IfTypeDerivesFromRazorFileInfoCollection()
{
// Arrange
var type = typeof(ViewCollection);
// Act
var result = CompilerCache.Match(type);
// Assert
Assert.True(result);
}
private abstract class AbstractRazorFileInfoCollection : RazorFileInfoCollection
{
}
private class GenericRazorFileInfoCollection<TVal> : RazorFileInfoCollection
{
}
private class ParameterConstructorRazorFileInfoCollection : RazorFileInfoCollection
{
public ParameterConstructorRazorFileInfoCollection(string value)
{
}
}
private class NonSubTypeRazorFileInfoCollection : Controller
{
}
private class ViewCollection : RazorFileInfoCollection
{
private readonly List<RazorFileInfo> _fileInfos = new List<RazorFileInfo>();
public ViewCollection()
{
FileInfos = _fileInfos;
var content = new PreCompile().Content;
var length = Encoding.UTF8.GetByteCount(content);
Add(new RazorFileInfo()
{
FullTypeName = typeof(PreCompile).FullName,
Hash = Crc32.Calculate(GetMemoryStream(content)).ToString(CultureInfo.InvariantCulture),
HashAlgorithmVersion = 1,
LastModified = DateTime.FromFileTimeUtc(10000),
Length = length,
RelativePath = ViewPath,
});
}
public void Add(RazorFileInfo fileInfo)
{
_fileInfos.Add(fileInfo);
}
public override Assembly LoadAssembly(IAssemblyLoadContext loadContext)
{
return typeof(ViewCollection).Assembly;
}
}
}
}

View File

@ -0,0 +1,13 @@
// 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 Xunit;
namespace Microsoft.AspNet.Mvc.Razor
{
public class DefaultPrecompiledViewsProviderTest
{
}
}

View File

@ -0,0 +1,20 @@
// 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 Microsoft.AspNet.Mvc;
namespace PrecompilationWebSite.Controllers
{
public class TagHelpersController : Controller
{
public IActionResult Add()
{
return View();
}
public IActionResult Remove()
{
return View();
}
}
}

View File

@ -0,0 +1,13 @@
// 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.ComponentModel.DataAnnotations;
namespace PrecompilationWebSite.Models
{
public class Person
{
[Range(10, 100)]
public int Age { get; set; }
}
}

View File

@ -0,0 +1,16 @@
// 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 Microsoft.AspNet.Razor.Runtime.TagHelpers;
namespace PrecompilationWebSite.TagHelpers
{
[HtmlElementName("root")]
public class RootViewStartTagHelper : TagHelper
{
public override void Process(TagHelperContext context, TagHelperOutput output)
{
output.Attributes["data-root"] = "true";
}
}
}

View File

@ -0,0 +1,3 @@
@model PrecompilationWebSite.Models.Person
@addTagHelper "*, Microsoft.AspNet.Mvc.TagHelpers"
<root><input asp-for="Age" class="form-control" /><a asp-action="Index">Back to List</a></root>

View File

@ -0,0 +1,2 @@
@removeTagHelper "*, PrecompilationWebSite"
<root>root-content</root>

View File

@ -0,0 +1 @@
@addTagHelper "PrecompilationWebSite.TagHelpers.RootViewStartTagHelper, PrecompilationWebSite"

View File

@ -0,0 +1,2 @@
@using System.Reflection
@GetType().GetTypeInfo().Assembly.FullName

View File

@ -6,6 +6,7 @@
"dependencies": {
"Kestrel": "1.0.0-*",
"Microsoft.AspNet.Mvc": "6.0.0-*",
"Microsoft.AspNet.Mvc.TagHelpers": "6.0.0-*",
"Microsoft.AspNet.Mvc.TestConfiguration": "1.0.0",
"Microsoft.AspNet.Server.IIS": "1.0.0-*",
"Microsoft.AspNet.Server.WebListener": "1.0.0-*",

View File

@ -4,15 +4,17 @@
using Microsoft.AspNet.Mvc;
using Microsoft.AspNet.Mvc.Razor;
using Microsoft.Framework.OptionsModel;
using Microsoft.Framework.Runtime;
namespace RazorCompilerCacheWebSite
{
public class CustomCompilerCache : CompilerCache
{
public CustomCompilerCache(IAssemblyProvider assemblyProvider,
IAssemblyLoadContextAccessor loadContextAccessor,
IOptions<RazorViewEngineOptions> optionsAccessor,
CompilerCacheInitialiedService cacheInitializedService)
: base(assemblyProvider, optionsAccessor)
: base(assemblyProvider, loadContextAccessor, optionsAccessor)
{
cacheInitializedService.Initialized = true;
}