* 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:
parent
6b3119e2c5
commit
57f5b19f25
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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; }
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -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>();
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -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";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
@removeTagHelper "*, PrecompilationWebSite"
|
||||
<root>root-content</root>
|
||||
|
|
@ -0,0 +1 @@
|
|||
@addTagHelper "PrecompilationWebSite.TagHelpers.RootViewStartTagHelper, PrecompilationWebSite"
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
@using System.Reflection
|
||||
@GetType().GetTypeInfo().Assembly.FullName
|
||||
|
|
@ -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-*",
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue