diff --git a/samples/MvcSample.Web/Compiler/Preprocess/RazorPreCompilation.cs b/samples/MvcSample.Web/Compiler/Preprocess/RazorPreCompilation.cs
index 014a3350cc..03f29c1685 100644
--- a/samples/MvcSample.Web/Compiler/Preprocess/RazorPreCompilation.cs
+++ b/samples/MvcSample.Web/Compiler/Preprocess/RazorPreCompilation.cs
@@ -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);
}
}
}
\ No newline at end of file
diff --git a/samples/TagHelperSample.Web/Compiler/Preprocess/RazorPreCompilation.cs b/samples/TagHelperSample.Web/Compiler/Preprocess/RazorPreCompilation.cs
new file mode 100644
index 0000000000..d7b5b87908
--- /dev/null
+++ b/samples/TagHelperSample.Web/Compiler/Preprocess/RazorPreCompilation.cs
@@ -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);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilerCache.cs b/src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilerCache.cs
index 26b705421b..4c9dcf4c7f 100644
--- a/src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilerCache.cs
+++ b/src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilerCache.cs
@@ -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
///
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 populated with precompiled views
/// discovered using .
///
- ///
- /// An representing the assemblies
- /// used to search for pre-compiled views.
- ///
+ /// The that provides assemblies
+ /// for precompiled view discovery.
+ /// The .
/// An accessor to the .
- public CompilerCache(IAssemblyProvider provider,
+ public CompilerCache(IAssemblyProvider assemblyProvider,
+ IAssemblyLoadContextAccessor loadContextAccessor,
IOptions 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,
+ internal CompilerCache(IEnumerable razorFileInfoCollections,
+ IAssemblyLoadContext loadContext,
IFileProvider fileProvider)
{
_fileProvider = fileProvider;
_cache = new MemoryCache(new MemoryCacheOptions { ListenForMemoryPressure = false });
+
var cacheEntries = new List();
- 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; }
diff --git a/src/Microsoft.AspNet.Mvc.Razor/Compilation/RoslynCompilationService.cs b/src/Microsoft.AspNet.Mvc.Razor/Compilation/RoslynCompilationService.cs
index a3bec60d63..3861756ac9 100644
--- a/src/Microsoft.AspNet.Mvc.Razor/Compilation/RoslynCompilationService.cs
+++ b/src/Microsoft.AspNet.Mvc.Razor/Compilation/RoslynCompilationService.cs
@@ -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
///
public class RoslynCompilationService : ICompilationService
{
- private readonly Lazy _supportsPdbGeneration = new Lazy(SupportsPdbGeneration);
+ private readonly Lazy _supportsPdbGeneration = new Lazy(SymbolsUtility.SupportsSymbolsGeneration);
private readonly ConcurrentDictionary _metadataFileCache =
new ConcurrentDictionary(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;
- }
}
}
diff --git a/src/Microsoft.AspNet.Mvc.Razor/Internal/SymbolsUtility.cs b/src/Microsoft.AspNet.Mvc.Razor/Internal/SymbolsUtility.cs
new file mode 100644
index 0000000000..d444e33973
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.Razor/Internal/SymbolsUtility.cs
@@ -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
+{
+ ///
+ /// Utility type for determining if a platform supports symbol file generation.
+ ///
+ public class SymbolsUtility
+ {
+ private const string SymWriterGuid = "0AE2DEB0-F901-478b-BB9F-881EE8066788";
+
+ ///
+ /// Determines if the current platform supports symbols (pdb) generation.
+ ///
+ /// true if pdb generation is supported; false otherwise.
+ 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;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Mvc.Razor/Properties/Resources.Designer.cs b/src/Microsoft.AspNet.Mvc.Razor/Properties/Resources.Designer.cs
index e6822d8316..c6e9045f13 100644
--- a/src/Microsoft.AspNet.Mvc.Razor/Properties/Resources.Designer.cs
+++ b/src/Microsoft.AspNet.Mvc.Razor/Properties/Resources.Designer.cs
@@ -378,6 +378,22 @@ namespace Microsoft.AspNet.Mvc.Razor
return GetString("RazorHash_UnsupportedHashAlgorithm");
}
+ ///
+ /// The resource '{0}' specified by '{1}' could not be found.
+ ///
+ internal static string RazorFileInfoCollection_ResourceCouldNotBeFound
+ {
+ get { return GetString("RazorFileInfoCollection_ResourceCouldNotBeFound"); }
+ }
+
+ ///
+ /// The resource '{0}' specified by '{1}' could not be found.
+ ///
+ 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);
diff --git a/src/Microsoft.AspNet.Mvc.Razor/Razor/PreCompileViews/PrecompilationTagHelperDescriptorResolver.cs b/src/Microsoft.AspNet.Mvc.Razor/Razor/PreCompileViews/PrecompilationTagHelperDescriptorResolver.cs
new file mode 100644
index 0000000000..5e4af6a28c
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.Razor/Razor/PreCompileViews/PrecompilationTagHelperDescriptorResolver.cs
@@ -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
+{
+ ///
+ /// used during Razor precompilation.
+ ///
+ 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;
+
+ ///
+ /// Initializes a new instance of .
+ ///
+ /// The .
+ /// The .
+ public PrecompilationTagHelperTypeResolver([NotNull] IBeforeCompileContext compileContext,
+ [NotNull] IAssemblyLoadContext loadContext)
+ {
+ _compileContext = compileContext;
+ _loadContext = loadContext;
+ }
+
+ ///
+ protected override IEnumerable 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();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Mvc.Razor/Razor/PreCompileViews/RazorFileInfoCollection.cs b/src/Microsoft.AspNet.Mvc.Razor/Razor/PreCompileViews/RazorFileInfoCollection.cs
index 412f628d71..b1d9aa55d0 100644
--- a/src/Microsoft.AspNet.Mvc.Razor/Razor/PreCompileViews/RazorFileInfoCollection.cs
+++ b/src/Microsoft.AspNet.Mvc.Razor/Razor/PreCompileViews/RazorFileInfoCollection.cs
@@ -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
{
+ ///
+ /// Specifies metadata about precompiled views.
+ ///
public abstract class RazorFileInfoCollection
{
+ ///
+ /// Gets or sets the name of the resource containing the precompiled binary.
+ ///
+ public string AssemblyResourceName { get; protected set; }
+
+ ///
+ /// Gets or sets the name of the resource that contains the symbols (pdb).
+ ///
+ public string SymbolsResourceName { get; protected set; }
+
+ ///
+ /// Gets the of s.
+ ///
public IReadOnlyList FileInfos { get; protected set; }
+
+ ///
+ /// Loads the assembly containing precompiled views.
+ ///
+ /// The .
+ /// The containing precompiled views.
+ 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);
+ }
+ }
+ }
}
}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Mvc.Razor/Razor/PreCompileViews/RazorFileInfoCollectionGenerator.cs b/src/Microsoft.AspNet.Mvc.Razor/Razor/PreCompileViews/RazorFileInfoCollectionGenerator.cs
index a09a252830..ae2ee25a44 100644
--- a/src/Microsoft.AspNet.Mvc.Razor/Razor/PreCompileViews/RazorFileInfoCollectionGenerator.cs
+++ b/src/Microsoft.AspNet.Mvc.Razor/Razor/PreCompileViews/RazorFileInfoCollectionGenerator.cs
@@ -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 fileInfos,
+ public RazorFileInfoCollectionGenerator([NotNull] RazorFileInfoCollection fileInfoCollection,
[NotNull] CompilationSettings compilationSettings)
{
- FileInfos = fileInfos;
+ RazorFileInfoCollection = fileInfoCollection;
CompilationSettings = compilationSettings;
}
- protected IEnumerable 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);
";
}
diff --git a/src/Microsoft.AspNet.Mvc.Razor/Razor/PreCompileViews/RazorPreCompiler.cs b/src/Microsoft.AspNet.Mvc.Razor/Razor/PreCompileViews/RazorPreCompiler.cs
index 06920098a8..2560c55a96 100644
--- a/src/Microsoft.AspNet.Mvc.Razor/Razor/PreCompileViews/RazorPreCompiler.cs
+++ b/src/Microsoft.AspNet.Mvc.Razor/Razor/PreCompileViews/RazorPreCompiler.cs
@@ -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(),
designTimeServiceProvider.GetRequiredService>(),
precompilationCache,
compilationSettings)
@@ -31,16 +40,30 @@ namespace Microsoft.AspNet.Mvc.Razor
}
public RazorPreCompiler([NotNull] IServiceProvider designTimeServiceProvider,
+ [NotNull] IBeforeCompileContext compileContext,
+ [NotNull] IAssemblyLoadContextAccessor loadContextAccessor,
[NotNull] IOptions 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);
}
+ ///
+ /// Gets or sets a value that determines if symbols (.pdb) file for the precompiled views.
+ ///
+ 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 CreateCompilationDescriptors(
- [NotNull] IBeforeCompileContext context)
+ protected virtual RazorFileInfoCollection CreateFileInfoCollection()
{
var filesToProcess = new List();
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 syntaxTrees,
+ [NotNull] IEnumerable 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();
+ 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(IList target, IEnumerable source)
+ {
+ foreach (var diagnostic in source)
+ {
+ target.Add(diagnostic);
+ }
+ }
+
+ private class PrecompileRazorFileInfoCollection : RazorFileInfoCollection
+ {
+ public PrecompileRazorFileInfoCollection(string assemblyResourceName,
+ string symbolsResourceName,
+ IReadOnlyList fileInfos)
+ {
+ AssemblyResourceName = assemblyResourceName;
+ SymbolsResourceName = symbolsResourceName;
+ FileInfos = fileInfos;
+ }
+ }
}
}
diff --git a/src/Microsoft.AspNet.Mvc.Razor/Resources.resx b/src/Microsoft.AspNet.Mvc.Razor/Resources.resx
index b032e9039c..5aaf28b864 100644
--- a/src/Microsoft.AspNet.Mvc.Razor/Resources.resx
+++ b/src/Microsoft.AspNet.Mvc.Razor/Resources.resx
@@ -186,4 +186,7 @@
Unsupported hash algorithm.
+
+ The resource '{0}' specified by '{1}' could not be found.
+
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Mvc/MvcServices.cs b/src/Microsoft.AspNet.Mvc/MvcServices.cs
index 276b72b062..35cfdc9e3f 100644
--- a/src/Microsoft.AspNet.Mvc/MvcServices.cs
+++ b/src/Microsoft.AspNet.Mvc/MvcServices.cs
@@ -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();
-
+
// Caches compilation artifacts across the lifetime of the application.
yield return describe.Singleton();
diff --git a/src/Microsoft.AspNet.Mvc/RazorPreCompileModule.cs b/src/Microsoft.AspNet.Mvc/RazorPreCompileModule.cs
index 0c1d67bfcd..91c2fb6665 100644
--- a/src/Microsoft.AspNet.Mvc/RazorPreCompileModule.cs
+++ b/src/Microsoft.AspNet.Mvc/RazorPreCompileModule.cs
@@ -31,6 +31,11 @@ namespace Microsoft.AspNet.Mvc
_memoryCache = new MemoryCache(new MemoryCacheOptions { ListenForMemoryPressure = false });
}
+ ///
+ /// Gets or sets a value that determines if symbols (.pdb) file for the precompiled views.
+ ///
+ 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)
diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/PrecompilationTest.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/PrecompilationTest.cs
index d41c55b839..75201cccf5 100644
--- a/test/Microsoft.AspNet.Mvc.FunctionalTests/PrecompilationTest.cs
+++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/PrecompilationTest.cs
@@ -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 = @"Back to List";
+ 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-content";
+ 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 TouchFile(string viewsDir, string file)
{
var path = Path.Combine(viewsDir, file);
diff --git a/test/Microsoft.AspNet.Mvc.Razor.Test/Compilation/CompilerCacheTest.cs b/test/Microsoft.AspNet.Mvc.Razor.Test/Compilation/CompilerCacheTest.cs
index b79820e1db..00ba6b46b5 100644
--- a/test/Microsoft.AspNet.Mvc.Razor.Test/Compilation/CompilerCacheTest.cs
+++ b/test/Microsoft.AspNet.Mvc.Razor.Test/Compilation/CompilerCacheTest.cs
@@ -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();
[Fact]
public void GetOrAdd_ReturnsFileNotFoundResult_IfFileIsNotFoundInFileSystem()
{
// Arrange
var fileProvider = new TestFileProvider();
- var cache = new CompilerCache(Enumerable.Empty(), fileProvider);
+ var cache = new CompilerCache(Enumerable.Empty(), 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(), fileProvider);
+ var cache = new CompilerCache(Enumerable.Empty(), 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(), fileProvider);
+ var cache = new CompilerCache(Enumerable.Empty(), 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(), fileProvider);
+ var cache = new CompilerCache(Enumerable.Empty(), 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 { CallBase = true };
var fileProvider = mockFileProvider.Object;
fileProvider.AddFile(ViewPath, "some content");
- var cache = new CompilerCache(Enumerable.Empty(), fileProvider);
+ var cache = new CompilerCache(Enumerable.Empty(), 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 _fileInfos = new List();
-
- 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(), fileProvider);
+ var cache = new CompilerCache(Enumerable.Empty(), 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 : RazorFileInfoCollection
+ {
+
+ }
+
+ private class ParameterConstructorRazorFileInfoCollection : RazorFileInfoCollection
+ {
+ public ParameterConstructorRazorFileInfoCollection(string value)
+ {
+ }
+ }
+
+ private class NonSubTypeRazorFileInfoCollection : Controller
+ {
+
+ }
+
+ private class ViewCollection : RazorFileInfoCollection
+ {
+ private readonly List _fileInfos = new List();
+
+ 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;
+ }
+ }
}
}
\ No newline at end of file
diff --git a/test/Microsoft.AspNet.Mvc.Razor.Test/DefaultPrecompiledViewsProviderTest.cs b/test/Microsoft.AspNet.Mvc.Razor.Test/DefaultPrecompiledViewsProviderTest.cs
new file mode 100644
index 0000000000..b59c9d3938
--- /dev/null
+++ b/test/Microsoft.AspNet.Mvc.Razor.Test/DefaultPrecompiledViewsProviderTest.cs
@@ -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
+ {
+
+ }
+}
\ No newline at end of file
diff --git a/test/WebSites/PrecompilationWebSite/Controllers/TagHelpersController.cs b/test/WebSites/PrecompilationWebSite/Controllers/TagHelpersController.cs
new file mode 100644
index 0000000000..60c60c1827
--- /dev/null
+++ b/test/WebSites/PrecompilationWebSite/Controllers/TagHelpersController.cs
@@ -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();
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/WebSites/PrecompilationWebSite/Models/Person.cs b/test/WebSites/PrecompilationWebSite/Models/Person.cs
new file mode 100644
index 0000000000..9ec8d58f16
--- /dev/null
+++ b/test/WebSites/PrecompilationWebSite/Models/Person.cs
@@ -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; }
+ }
+}
\ No newline at end of file
diff --git a/test/WebSites/PrecompilationWebSite/TagHelpers/RootViewStartTagHelper.cs b/test/WebSites/PrecompilationWebSite/TagHelpers/RootViewStartTagHelper.cs
new file mode 100644
index 0000000000..25137e43b7
--- /dev/null
+++ b/test/WebSites/PrecompilationWebSite/TagHelpers/RootViewStartTagHelper.cs
@@ -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";
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/WebSites/PrecompilationWebSite/Views/TagHelpers/Add.cshtml b/test/WebSites/PrecompilationWebSite/Views/TagHelpers/Add.cshtml
new file mode 100644
index 0000000000..55726c8d2e
--- /dev/null
+++ b/test/WebSites/PrecompilationWebSite/Views/TagHelpers/Add.cshtml
@@ -0,0 +1,3 @@
+@model PrecompilationWebSite.Models.Person
+@addTagHelper "*, Microsoft.AspNet.Mvc.TagHelpers"
+Back to List
diff --git a/test/WebSites/PrecompilationWebSite/Views/TagHelpers/Remove.cshtml b/test/WebSites/PrecompilationWebSite/Views/TagHelpers/Remove.cshtml
new file mode 100644
index 0000000000..11c91d0c88
--- /dev/null
+++ b/test/WebSites/PrecompilationWebSite/Views/TagHelpers/Remove.cshtml
@@ -0,0 +1,2 @@
+@removeTagHelper "*, PrecompilationWebSite"
+root-content
diff --git a/test/WebSites/PrecompilationWebSite/Views/TagHelpers/_GlobalImport.cshtml b/test/WebSites/PrecompilationWebSite/Views/TagHelpers/_GlobalImport.cshtml
new file mode 100644
index 0000000000..8560f82d73
--- /dev/null
+++ b/test/WebSites/PrecompilationWebSite/Views/TagHelpers/_GlobalImport.cshtml
@@ -0,0 +1 @@
+@addTagHelper "PrecompilationWebSite.TagHelpers.RootViewStartTagHelper, PrecompilationWebSite"
diff --git a/test/WebSites/PrecompilationWebSite/Views/TagHelpers/_ViewStart.cshtml b/test/WebSites/PrecompilationWebSite/Views/TagHelpers/_ViewStart.cshtml
new file mode 100644
index 0000000000..4a10269678
--- /dev/null
+++ b/test/WebSites/PrecompilationWebSite/Views/TagHelpers/_ViewStart.cshtml
@@ -0,0 +1,2 @@
+@using System.Reflection
+@GetType().GetTypeInfo().Assembly.FullName
diff --git a/test/WebSites/PrecompilationWebSite/project.json b/test/WebSites/PrecompilationWebSite/project.json
index 3f6e124d78..baa2940d45 100644
--- a/test/WebSites/PrecompilationWebSite/project.json
+++ b/test/WebSites/PrecompilationWebSite/project.json
@@ -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-*",
diff --git a/test/WebSites/RazorCompilerCacheWebSite/Services/CustomCompilerCache.cs b/test/WebSites/RazorCompilerCacheWebSite/Services/CustomCompilerCache.cs
index af0df88b97..288b9ab3e7 100644
--- a/test/WebSites/RazorCompilerCacheWebSite/Services/CustomCompilerCache.cs
+++ b/test/WebSites/RazorCompilerCacheWebSite/Services/CustomCompilerCache.cs
@@ -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 optionsAccessor,
CompilerCacheInitialiedService cacheInitializedService)
- : base(assemblyProvider, optionsAccessor)
+ : base(assemblyProvider, loadContextAccessor, optionsAccessor)
{
cacheInitializedService.Initialized = true;
}