* Remove support for updateable precompiled views.
* Allow precompiled views to be served when source file does not exist in file system. * Cache results for views that do not exist on disk. Fixes #2462 and fixes #2796. Partially addresses #2551
This commit is contained in:
parent
982213e9e0
commit
dfacd2543b
|
|
@ -3,23 +3,22 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Microsoft.AspNet.FileProviders;
|
||||
using Microsoft.AspNet.Mvc.Razor.Precompilation;
|
||||
using Microsoft.Dnx.Runtime;
|
||||
using Microsoft.Framework.Caching.Memory;
|
||||
using Microsoft.Framework.Internal;
|
||||
using Microsoft.Framework.OptionsModel;
|
||||
using Microsoft.Dnx.Runtime;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.Razor.Compilation
|
||||
{
|
||||
/// <summary>
|
||||
/// Caches the result of runtime compilation of Razor files for the duration of the app lifetime.
|
||||
/// Caches the result of runtime compilation of Razor files for the duration of the application lifetime.
|
||||
/// </summary>
|
||||
public class CompilerCache : ICompilerCache
|
||||
{
|
||||
private static readonly TypeInfo RazorFileInfoCollectionType = typeof(RazorFileInfoCollection).GetTypeInfo();
|
||||
private static readonly Assembly RazorHostAssembly = typeof(CompilerCache).GetTypeInfo().Assembly;
|
||||
private readonly IFileProvider _fileProvider;
|
||||
private readonly IMemoryCache _cache;
|
||||
|
||||
|
|
@ -27,178 +26,79 @@ namespace Microsoft.AspNet.Mvc.Razor.Compilation
|
|||
/// Initializes a new instance of <see cref="CompilerCache"/> populated with precompiled views
|
||||
/// discovered using <paramref name="provider"/>.
|
||||
/// </summary>
|
||||
/// <param name="assemblyProvider">The <see cref="IAssemblyProvider"/> that provides assemblies
|
||||
/// for precompiled view discovery.</param>
|
||||
/// <param name="razorFileInfoCollections">The sequence of <see cref="RazorFileInfoCollection"/> that provides
|
||||
/// information 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 assemblyProvider,
|
||||
IAssemblyLoadContextAccessor loadContextAccessor,
|
||||
IOptions<RazorViewEngineOptions> optionsAccessor)
|
||||
: this(GetFileInfos(assemblyProvider.CandidateAssemblies),
|
||||
loadContextAccessor.GetLoadContext(RazorFileInfoCollectionType.Assembly),
|
||||
public CompilerCache(
|
||||
IEnumerable<RazorFileInfoCollection> razorFileInfoCollections,
|
||||
IAssemblyLoadContextAccessor loadContextAccessor,
|
||||
IOptions<RazorViewEngineOptions> optionsAccessor)
|
||||
: this(razorFileInfoCollections,
|
||||
loadContextAccessor.GetLoadContext(RazorHostAssembly),
|
||||
optionsAccessor.Options.FileProvider)
|
||||
{
|
||||
}
|
||||
|
||||
internal CompilerCache(IEnumerable<RazorFileInfoCollection> razorFileInfoCollections,
|
||||
IAssemblyLoadContext loadContext,
|
||||
IFileProvider fileProvider)
|
||||
internal CompilerCache(
|
||||
IEnumerable<RazorFileInfoCollection> razorFileInfoCollections,
|
||||
IAssemblyLoadContext loadContext,
|
||||
IFileProvider fileProvider)
|
||||
{
|
||||
_fileProvider = fileProvider;
|
||||
_cache = new MemoryCache(new MemoryCacheOptions { CompactOnMemoryPressure = false });
|
||||
|
||||
var cacheEntries = new List<CompilerCacheEntry>();
|
||||
foreach (var viewCollection in razorFileInfoCollections)
|
||||
{
|
||||
var containingAssembly = viewCollection.LoadAssembly(loadContext);
|
||||
foreach (var fileInfo in viewCollection.FileInfos)
|
||||
{
|
||||
var viewType = containingAssembly.GetType(fileInfo.FullTypeName);
|
||||
var cacheEntry = new CompilerCacheEntry(fileInfo, viewType);
|
||||
|
||||
// There shouldn't be any duplicates and if there are any the first will win.
|
||||
// If the result doesn't match the one on disk its going to recompile anyways.
|
||||
var cacheEntry = new CompilerCacheResult(CompilationResult.Successful(viewType));
|
||||
var normalizedPath = NormalizePath(fileInfo.RelativePath);
|
||||
_cache.Set(
|
||||
normalizedPath,
|
||||
cacheEntry,
|
||||
GetMemoryCacheEntryOptions(fileInfo.RelativePath));
|
||||
|
||||
cacheEntries.Add(cacheEntry);
|
||||
}
|
||||
}
|
||||
|
||||
// Set up _ViewImports
|
||||
foreach (var entry in cacheEntries)
|
||||
{
|
||||
var globalFileLocations = ViewHierarchyUtility.GetViewImportsLocations(entry.RelativePath);
|
||||
foreach (var location in globalFileLocations)
|
||||
{
|
||||
var globalFileEntry = _cache.Get<CompilerCacheEntry>(location);
|
||||
if (globalFileEntry != null)
|
||||
{
|
||||
// Add the composite _ViewImports entry as a dependency.
|
||||
entry.AssociatedGlobalFileEntry = globalFileEntry;
|
||||
break;
|
||||
}
|
||||
_cache.Set(normalizedPath, cacheEntry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public CompilerCacheResult GetOrAdd([NotNull] string relativePath,
|
||||
[NotNull] Func<RelativeFileInfo, CompilationResult> compile)
|
||||
{
|
||||
var result = GetOrAddCore(relativePath, compile);
|
||||
if (result == null)
|
||||
{
|
||||
return CompilerCacheResult.FileNotFound;
|
||||
}
|
||||
|
||||
return new CompilerCacheResult(result.CompilationResult);
|
||||
}
|
||||
|
||||
private GetOrAddResult GetOrAddCore(string relativePath,
|
||||
Func<RelativeFileInfo, CompilationResult> compile)
|
||||
public CompilerCacheResult GetOrAdd(
|
||||
[NotNull] string relativePath,
|
||||
[NotNull] Func<RelativeFileInfo, CompilationResult> compile)
|
||||
{
|
||||
var normalizedPath = NormalizePath(relativePath);
|
||||
var cacheEntry = _cache.Get<CompilerCacheEntry>(normalizedPath);
|
||||
if (cacheEntry == null)
|
||||
CompilerCacheResult cacheResult;
|
||||
if (!_cache.TryGetValue(normalizedPath, out cacheResult))
|
||||
{
|
||||
var fileInfo = _fileProvider.GetFileInfo(relativePath);
|
||||
MemoryCacheEntryOptions cacheEntryOptions;
|
||||
CompilerCacheResult cacheResultToCache;
|
||||
if (!fileInfo.Exists)
|
||||
{
|
||||
return null;
|
||||
cacheResultToCache = CompilerCacheResult.FileNotFound;
|
||||
cacheResult = CompilerCacheResult.FileNotFound;
|
||||
|
||||
cacheEntryOptions = new MemoryCacheEntryOptions();
|
||||
cacheEntryOptions.AddExpirationTrigger(_fileProvider.Watch(relativePath));
|
||||
}
|
||||
|
||||
var relativeFileInfo = new RelativeFileInfo(fileInfo, relativePath);
|
||||
return OnCacheMiss(relativeFileInfo, normalizedPath, compile);
|
||||
}
|
||||
else if (cacheEntry.IsPreCompiled && !cacheEntry.IsValidatedPreCompiled)
|
||||
{
|
||||
// For precompiled views, the first time the entry is read, we need to ensure that no changes were made
|
||||
// either to the file associated with this entry, or any _ViewImports associated with it between the time
|
||||
// the View was precompiled and the time EnsureInitialized was called. For later iterations, we can
|
||||
// rely on expiration triggers ensuring the validity of the entry.
|
||||
|
||||
var fileInfo = _fileProvider.GetFileInfo(relativePath);
|
||||
if (!fileInfo.Exists)
|
||||
else
|
||||
{
|
||||
return null;
|
||||
var relativeFileInfo = new RelativeFileInfo(fileInfo, relativePath);
|
||||
var compilationResult = compile(relativeFileInfo).EnsureSuccessful();
|
||||
cacheEntryOptions = GetMemoryCacheEntryOptions(relativePath);
|
||||
|
||||
// By default the CompilationResult returned by IRoslynCompiler is an instance of
|
||||
// UncachedCompilationResult. This type has the generated code as a string property and do not want
|
||||
// to cache it. We'll instead cache the unwrapped result.
|
||||
cacheResultToCache = new CompilerCacheResult(
|
||||
CompilationResult.Successful(compilationResult.CompiledType));
|
||||
cacheResult = new CompilerCacheResult(compilationResult);
|
||||
}
|
||||
|
||||
var relativeFileInfo = new RelativeFileInfo(fileInfo, relativePath);
|
||||
if (cacheEntry.Length != fileInfo.Length)
|
||||
{
|
||||
// Recompile if the file lengths differ
|
||||
return OnCacheMiss(relativeFileInfo, normalizedPath, compile);
|
||||
}
|
||||
|
||||
if (AssociatedGlobalFilesChanged(cacheEntry, compile))
|
||||
{
|
||||
// Recompile if _ViewImports have changed since the entry was created.
|
||||
return OnCacheMiss(relativeFileInfo, normalizedPath, compile);
|
||||
}
|
||||
|
||||
if (cacheEntry.LastModified == fileInfo.LastModified)
|
||||
{
|
||||
// Assigning to IsValidatedPreCompiled is an atomic operation and will result in a safe race
|
||||
// if it is being concurrently updated and read.
|
||||
cacheEntry.IsValidatedPreCompiled = true;
|
||||
return new GetOrAddResult
|
||||
{
|
||||
CompilationResult = CompilationResult.Successful(cacheEntry.CompiledType),
|
||||
CompilerCacheEntry = cacheEntry
|
||||
};
|
||||
}
|
||||
|
||||
// Timestamp doesn't match but it might be because of deployment, compare the hash.
|
||||
if (cacheEntry.IsPreCompiled &&
|
||||
string.Equals(cacheEntry.Hash,
|
||||
RazorFileHash.GetHash(fileInfo, cacheEntry.HashAlgorithmVersion),
|
||||
StringComparison.Ordinal))
|
||||
{
|
||||
// Cache hit, but we need to update the entry.
|
||||
// Assigning to LastModified and IsValidatedPreCompiled are atomic operations and will result in safe race
|
||||
// if the entry is being concurrently read or updated.
|
||||
cacheEntry.LastModified = fileInfo.LastModified;
|
||||
cacheEntry.IsValidatedPreCompiled = true;
|
||||
return new GetOrAddResult
|
||||
{
|
||||
CompilationResult = CompilationResult.Successful(cacheEntry.CompiledType),
|
||||
CompilerCacheEntry = cacheEntry
|
||||
};
|
||||
}
|
||||
|
||||
// it's not a match, recompile
|
||||
return OnCacheMiss(relativeFileInfo, normalizedPath, compile);
|
||||
_cache.Set(normalizedPath, cacheResultToCache, cacheEntryOptions);
|
||||
}
|
||||
|
||||
return new GetOrAddResult
|
||||
{
|
||||
CompilationResult = CompilationResult.Successful(cacheEntry.CompiledType),
|
||||
CompilerCacheEntry = cacheEntry
|
||||
};
|
||||
}
|
||||
|
||||
private GetOrAddResult OnCacheMiss(RelativeFileInfo file,
|
||||
string normalizedPath,
|
||||
Func<RelativeFileInfo, CompilationResult> compile)
|
||||
{
|
||||
var compilationResult = compile(file).EnsureSuccessful();
|
||||
|
||||
// Concurrent addition to MemoryCache with the same key result in safe race.
|
||||
var compilerCacheEntry = new CompilerCacheEntry(file, compilationResult.CompiledType);
|
||||
var cacheEntry = _cache.Set<CompilerCacheEntry>(
|
||||
normalizedPath,
|
||||
compilerCacheEntry,
|
||||
GetMemoryCacheEntryOptions(compilerCacheEntry.RelativePath));
|
||||
|
||||
return new GetOrAddResult
|
||||
{
|
||||
CompilationResult = compilationResult,
|
||||
CompilerCacheEntry = cacheEntry
|
||||
};
|
||||
return cacheResult;
|
||||
}
|
||||
|
||||
private MemoryCacheEntryOptions GetMemoryCacheEntryOptions(string relativePath)
|
||||
|
|
@ -211,37 +111,10 @@ namespace Microsoft.AspNet.Mvc.Razor.Compilation
|
|||
{
|
||||
options.AddExpirationTrigger(_fileProvider.Watch(location));
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
private bool AssociatedGlobalFilesChanged(CompilerCacheEntry entry,
|
||||
Func<RelativeFileInfo, CompilationResult> compile)
|
||||
{
|
||||
var globalFileEntry = GetCompositeGlobalFileEntry(entry.RelativePath, compile);
|
||||
return entry.AssociatedGlobalFileEntry != globalFileEntry;
|
||||
}
|
||||
|
||||
// Returns the entry for the nearest _ViewImports that the file inherits directives from. Since _ViewImports
|
||||
// entries are affected by other _ViewImports entries that are in the path hierarchy, the returned value
|
||||
// represents the composite result of performing a cache check on individual _ViewImports entries.
|
||||
private CompilerCacheEntry GetCompositeGlobalFileEntry(string relativePath,
|
||||
Func<RelativeFileInfo, CompilationResult> compile)
|
||||
{
|
||||
var viewImportsLocations = ViewHierarchyUtility.GetViewImportsLocations(relativePath);
|
||||
foreach (var viewImports in viewImportsLocations)
|
||||
{
|
||||
var getOrAddResult = GetOrAddCore(viewImports, compile);
|
||||
if (getOrAddResult != null)
|
||||
{
|
||||
// This is the nearest _ViewImports that exists on disk.
|
||||
return getOrAddResult.CompilerCacheEntry;
|
||||
}
|
||||
}
|
||||
|
||||
// No _ViewImports discovered.
|
||||
return null;
|
||||
}
|
||||
|
||||
private static string NormalizePath(string path)
|
||||
{
|
||||
// We need to allow for scenarios where the application was precompiled on a machine with forward slashes
|
||||
|
|
@ -252,34 +125,5 @@ namespace Microsoft.AspNet.Mvc.Razor.Compilation
|
|||
|
||||
return path;
|
||||
}
|
||||
|
||||
internal static IEnumerable<RazorFileInfoCollection> GetFileInfos(IEnumerable<Assembly> assemblies)
|
||||
{
|
||||
return assemblies.SelectMany(a => a.ExportedTypes)
|
||||
.Where(Match)
|
||||
.Select(c => (RazorFileInfoCollection)Activator.CreateInstance(c));
|
||||
}
|
||||
|
||||
internal static bool Match(Type t)
|
||||
{
|
||||
var inAssemblyType = typeof(RazorFileInfoCollection);
|
||||
if (inAssemblyType.IsAssignableFrom(t))
|
||||
{
|
||||
var hasParameterlessConstructor = t.GetConstructor(Type.EmptyTypes) != null;
|
||||
|
||||
return hasParameterlessConstructor
|
||||
&& !t.GetTypeInfo().IsAbstract
|
||||
&& !t.GetTypeInfo().ContainsGenericParameters;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private class GetOrAddResult
|
||||
{
|
||||
public CompilerCacheEntry CompilerCacheEntry { get; set; }
|
||||
|
||||
public CompilationResult CompilationResult { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,93 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. 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.Razor.Precompilation;
|
||||
using Microsoft.Framework.Internal;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.Razor.Compilation
|
||||
{
|
||||
/// <summary>
|
||||
/// An entry in <see cref="ICompilerCache"/> that contain metadata about precompiled and dynamically compiled file.
|
||||
/// </summary>
|
||||
public class CompilerCacheEntry
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of <see cref="CompilerCacheEntry"/> for a file that was precompiled.
|
||||
/// </summary>
|
||||
/// <param name="info">Metadata about the precompiled file.</param>
|
||||
/// <param name="compiledType">The compiled <see cref="Type"/>.</param>
|
||||
public CompilerCacheEntry([NotNull] RazorFileInfo info, [NotNull] Type compiledType)
|
||||
{
|
||||
CompiledType = compiledType;
|
||||
RelativePath = info.RelativePath;
|
||||
Length = info.Length;
|
||||
LastModified = info.LastModified;
|
||||
Hash = info.Hash;
|
||||
HashAlgorithmVersion = info.HashAlgorithmVersion;
|
||||
IsPreCompiled = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of <see cref="CompilerCacheEntry"/> for a file that was dynamically compiled.
|
||||
/// </summary>
|
||||
/// <param name="info">Metadata about the file that was compiled.</param>
|
||||
/// <param name="compiledType">The compiled <see cref="Type"/>.</param>
|
||||
public CompilerCacheEntry([NotNull] RelativeFileInfo info, [NotNull] Type compiledType)
|
||||
{
|
||||
CompiledType = compiledType;
|
||||
RelativePath = info.RelativePath;
|
||||
Length = info.FileInfo.Length;
|
||||
LastModified = info.FileInfo.LastModified;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="Type"/> produced as a result of compilation.
|
||||
/// </summary>
|
||||
public Type CompiledType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the path of the compiled file relative to the root of the application.
|
||||
/// </summary>
|
||||
public string RelativePath { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the size of file (in bytes) on disk.
|
||||
/// </summary>
|
||||
public long Length { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the last modified <see cref="DateTimeOffset"/> for the file at the time of compilation.
|
||||
/// </summary>
|
||||
public DateTimeOffset LastModified { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the file hash, should only be available for pre compiled files.
|
||||
/// </summary>
|
||||
public string Hash { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the version of the hash algorithm used to generate <see cref="Hash"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This value is only initialized for precompiled views.
|
||||
/// </remarks>
|
||||
public int HashAlgorithmVersion { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a flag that indicates if the file is precompiled.
|
||||
/// </summary>
|
||||
public bool IsPreCompiled { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="CompilerCacheEntry"/> for the nearest _ViewImports that the compiled type
|
||||
/// depends on.
|
||||
/// </summary>
|
||||
public CompilerCacheEntry AssociatedGlobalFileEntry { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a flag that determines if the validity of this cache entry was performed at runtime.
|
||||
/// </summary>
|
||||
public bool IsValidatedPreCompiled { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -2,10 +2,14 @@
|
|||
// 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.Linq;
|
||||
using System.Reflection;
|
||||
using Microsoft.AspNet.Mvc;
|
||||
using Microsoft.AspNet.Mvc.Razor;
|
||||
using Microsoft.AspNet.Mvc.Razor.Compilation;
|
||||
using Microsoft.AspNet.Mvc.Razor.Directives;
|
||||
using Microsoft.AspNet.Mvc.Razor.Precompilation;
|
||||
using Microsoft.Framework.Caching.Memory;
|
||||
using Microsoft.Framework.Internal;
|
||||
using Microsoft.Framework.OptionsModel;
|
||||
|
|
@ -14,6 +18,8 @@ namespace Microsoft.Framework.DependencyInjection
|
|||
{
|
||||
public static class MvcRazorMvcBuilderExtensions
|
||||
{
|
||||
private static readonly Type RazorFileInfoCollectionType = typeof(RazorFileInfoCollection);
|
||||
|
||||
public static IMvcBuilder AddRazorViewEngine([NotNull] this IMvcBuilder builder)
|
||||
{
|
||||
builder.AddViews();
|
||||
|
|
@ -36,6 +42,28 @@ namespace Microsoft.Framework.DependencyInjection
|
|||
return builder;
|
||||
}
|
||||
|
||||
public static IMvcBuilder AddPrecompiledRazorViews(
|
||||
[NotNull] this IMvcBuilder builder,
|
||||
[NotNull] params Assembly[] assemblies)
|
||||
{
|
||||
AddRazorViewEngine(builder);
|
||||
|
||||
var razorFileInfos = GetFileInfoCollections(assemblies);
|
||||
builder.Services.TryAddEnumerable(ServiceDescriptor.Instance(razorFileInfos));
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
public static IServiceCollection AddPrecompiledRazorViews(
|
||||
[NotNull] this IServiceCollection collection,
|
||||
[NotNull] params Assembly[] assemblies)
|
||||
{
|
||||
var razorFileInfos = GetFileInfoCollections(assemblies);
|
||||
collection.TryAddEnumerable(ServiceDescriptor.Instance(razorFileInfos));
|
||||
|
||||
return collection;
|
||||
}
|
||||
|
||||
public static IMvcBuilder ConfigureRazorViewEngine(
|
||||
[NotNull] this IMvcBuilder builder,
|
||||
[NotNull] Action<RazorViewEngineOptions> setupAction)
|
||||
|
|
@ -91,5 +119,17 @@ namespace Microsoft.Framework.DependencyInjection
|
|||
// Consumed by the Cache tag helper to cache results across the lifetime of the application.
|
||||
services.TryAddSingleton<IMemoryCache, MemoryCache>();
|
||||
}
|
||||
|
||||
private static IEnumerable<RazorFileInfoCollection> GetFileInfoCollections(IEnumerable<Assembly> assemblies) =>
|
||||
assemblies
|
||||
.SelectMany(assembly => assembly.ExportedTypes)
|
||||
.Where(IsValidRazorFileInfoCollection)
|
||||
.Select(Activator.CreateInstance)
|
||||
.Cast<RazorFileInfoCollection>();
|
||||
|
||||
internal static bool IsValidRazorFileInfoCollection(Type type) =>
|
||||
RazorFileInfoCollectionType.IsAssignableFrom(type) &&
|
||||
!type.GetTypeInfo().IsAbstract &&
|
||||
!type.GetTypeInfo().ContainsGenericParameters;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Precompilation
|
|||
public string SymbolsResourceName { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="IReadOnlyList{T}{T}"/> of <see cref="RazorFileInfo"/>s.
|
||||
/// Gets the <see cref="IReadOnlyList{T}"/> of <see cref="RazorFileInfo"/>s.
|
||||
/// </summary>
|
||||
public IReadOnlyList<RazorFileInfo> FileInfos { get; protected set; }
|
||||
|
||||
|
|
|
|||
|
|
@ -41,10 +41,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
|||
var applicationEnvironment = serviceProvider.GetRequiredService<IApplicationEnvironment>();
|
||||
|
||||
var viewsDirectory = Path.Combine(applicationEnvironment.ApplicationBasePath, "Views", "Home");
|
||||
var layoutContent = File.ReadAllText(Path.Combine(viewsDirectory, "Layout.cshtml"));
|
||||
var indexContent = File.ReadAllText(Path.Combine(viewsDirectory, "Index.cshtml"));
|
||||
var viewstartContent = File.ReadAllText(Path.Combine(viewsDirectory, "_ViewStart.cshtml"));
|
||||
var globalContent = File.ReadAllText(Path.Combine(viewsDirectory, "_ViewImports.cshtml"));
|
||||
|
||||
// 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.
|
||||
|
|
@ -64,103 +61,19 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
|||
Assert.StartsWith(assemblyNamePrefix, parsedResponse1.Index);
|
||||
|
||||
// Act - 2
|
||||
// Touch the Layout file and verify it is now dynamically compiled.
|
||||
await TouchFile(viewsDirectory, "Layout.cshtml");
|
||||
// Touch the Index file and verify it remains unaffected.
|
||||
await TouchFile(viewsDirectory, "Index.cshtml");
|
||||
responseContent = await client.GetStringAsync("http://localhost/Home/Index");
|
||||
|
||||
// Assert - 2
|
||||
var response2 = new ParsedResponse(responseContent);
|
||||
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.
|
||||
await TouchFile(viewsDirectory, "_ViewStart.cshtml");
|
||||
responseContent = await client.GetStringAsync("http://localhost/Home/Index");
|
||||
|
||||
// Assert - 3
|
||||
var response3 = new ParsedResponse(responseContent);
|
||||
Assert.NotEqual(assemblyNamePrefix, response3.ViewStart);
|
||||
Assert.Equal(response2.Index, response3.Index);
|
||||
Assert.Equal(response2.Layout, response3.Layout);
|
||||
|
||||
// Act - 4
|
||||
// Touch the _ViewImports file and verify it causes all files to recompile.
|
||||
await TouchFile(viewsDirectory, "_ViewImports.cshtml");
|
||||
responseContent = await client.GetStringAsync("http://localhost/Home/Index");
|
||||
|
||||
// Assert - 4
|
||||
var response4 = new ParsedResponse(responseContent);
|
||||
Assert.NotEqual(response3.ViewStart, response4.ViewStart);
|
||||
Assert.NotEqual(response3.Index, response4.Index);
|
||||
Assert.NotEqual(response3.Layout, response4.Layout);
|
||||
|
||||
// Act - 5
|
||||
// Touch Index file and verify it is the only page that recompiles.
|
||||
await TouchFile(viewsDirectory, "Index.cshtml");
|
||||
responseContent = await client.GetStringAsync("http://localhost/Home/Index");
|
||||
|
||||
// Assert - 5
|
||||
var response5 = new ParsedResponse(responseContent);
|
||||
// Layout and _ViewStart should not have changed.
|
||||
Assert.Equal(response4.Layout, response5.Layout);
|
||||
Assert.Equal(response4.ViewStart, response5.ViewStart);
|
||||
Assert.NotEqual(response4.Index, response5.Index);
|
||||
|
||||
// Act - 6
|
||||
// Touch the _ViewImports file. This time, we'll verify the Non-precompiled -> Non-precompiled workflow.
|
||||
await TouchFile(viewsDirectory, "_ViewImports.cshtml");
|
||||
responseContent = await client.GetStringAsync("http://localhost/Home/Index");
|
||||
|
||||
// Assert - 6
|
||||
var response6 = new ParsedResponse(responseContent);
|
||||
// Everything should've recompiled.
|
||||
Assert.NotEqual(response5.ViewStart, response6.ViewStart);
|
||||
Assert.NotEqual(response5.Index, response6.Index);
|
||||
Assert.NotEqual(response5.Layout, response6.Layout);
|
||||
|
||||
// Act - 7
|
||||
// Add a new _ViewImports file
|
||||
var newViewImports = await TouchFile(Path.GetDirectoryName(viewsDirectory), "_ViewImports.cshtml");
|
||||
responseContent = await client.GetStringAsync("http://localhost/Home/Index");
|
||||
|
||||
// Assert - 7
|
||||
// Everything should've recompiled.
|
||||
var response7 = new ParsedResponse(responseContent);
|
||||
Assert.NotEqual(response6.ViewStart, response7.ViewStart);
|
||||
Assert.NotEqual(response6.Index, response7.Index);
|
||||
Assert.NotEqual(response6.Layout, response7.Layout);
|
||||
|
||||
// Act - 8
|
||||
// Remove new _ViewImports file
|
||||
File.Delete(newViewImports);
|
||||
await Task.Delay(_cacheDelayInterval);
|
||||
responseContent = await client.GetStringAsync("http://localhost/Home/Index");
|
||||
|
||||
// Assert - 8
|
||||
// Everything should've recompiled.
|
||||
var response8 = new ParsedResponse(responseContent);
|
||||
Assert.NotEqual(response7.ViewStart, response8.ViewStart);
|
||||
Assert.NotEqual(response7.Index, response8.Index);
|
||||
Assert.NotEqual(response7.Layout, response8.Layout);
|
||||
|
||||
// Act - 9
|
||||
// Refetch and verify we get cached types
|
||||
responseContent = await client.GetStringAsync("http://localhost/Home/Index");
|
||||
|
||||
// Assert - 9
|
||||
var response9 = new ParsedResponse(responseContent);
|
||||
Assert.Equal(response8.ViewStart, response9.ViewStart);
|
||||
Assert.Equal(response8.Index, response9.Index);
|
||||
Assert.Equal(response8.Layout, response9.Layout);
|
||||
Assert.StartsWith(assemblyNamePrefix, response2.Layout);
|
||||
}
|
||||
finally
|
||||
{
|
||||
File.WriteAllText(Path.Combine(viewsDirectory, "Layout.cshtml"), layoutContent.TrimEnd(' '));
|
||||
File.WriteAllText(Path.Combine(viewsDirectory, "Index.cshtml"), indexContent.TrimEnd(' '));
|
||||
File.WriteAllText(Path.Combine(viewsDirectory, "_ViewStart.cshtml"), viewstartContent.TrimEnd(' '));
|
||||
File.WriteAllText(Path.Combine(viewsDirectory, "_ViewImports.cshtml"), globalContent.TrimEnd(' '));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -188,51 +101,6 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
|||
Assert.StartsWith(expected, responseContent.Trim());
|
||||
}
|
||||
|
||||
[ConditionalTheory]
|
||||
[FrameworkSkipCondition(RuntimeFrameworks.Mono)]
|
||||
public async Task DeletingPrecompiledGlobalFile_PriorToFirstRequestToAView_CausesViewToBeRecompiled()
|
||||
{
|
||||
// Arrange
|
||||
var expected = GetAssemblyNamePrefix();
|
||||
IServiceCollection serviceCollection = null;
|
||||
var server = TestHelper.CreateServer(_app, SiteName, services =>
|
||||
{
|
||||
_configureServices(services);
|
||||
serviceCollection = services;
|
||||
});
|
||||
var client = server.CreateClient();
|
||||
|
||||
var serviceProvider = serviceCollection.BuildServiceProvider();
|
||||
var applicationEnvironment = serviceProvider.GetRequiredService<IApplicationEnvironment>();
|
||||
|
||||
var viewsDirectory = Path.Combine(applicationEnvironment.ApplicationBasePath,
|
||||
"Views",
|
||||
"ViewImportsDelete");
|
||||
var globalPath = Path.Combine(viewsDirectory, "_ViewImports.cshtml");
|
||||
var globalContent = File.ReadAllText(globalPath);
|
||||
|
||||
// Act - 1
|
||||
// Query the Test view so we know the compiler cache gets populated.
|
||||
var response = await client.GetStringAsync("/Test");
|
||||
|
||||
// Assert - 1
|
||||
Assert.Equal("Test", response.Trim());
|
||||
|
||||
try
|
||||
{
|
||||
// Act - 2
|
||||
File.Delete(globalPath);
|
||||
var response2 = await client.GetStringAsync("http://localhost/Home/GlobalDeletedPriorToFirstRequest");
|
||||
|
||||
// Assert - 2
|
||||
Assert.DoesNotContain(expected, response2.Trim());
|
||||
}
|
||||
finally
|
||||
{
|
||||
File.WriteAllText(globalPath, globalContent);
|
||||
}
|
||||
}
|
||||
|
||||
[ConditionalTheory]
|
||||
[FrameworkSkipCondition(RuntimeFrameworks.Mono)]
|
||||
public async Task TagHelpersFromTheApplication_CanBeAdded()
|
||||
|
|
|
|||
|
|
@ -3,15 +3,10 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using Microsoft.AspNet.FileProviders;
|
||||
using Microsoft.AspNet.Mvc.Razor.Internal;
|
||||
using Microsoft.AspNet.Mvc.Razor.Precompilation;
|
||||
using Microsoft.AspNet.Testing.xunit;
|
||||
using Microsoft.Dnx.Runtime;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
|
@ -20,19 +15,27 @@ namespace Microsoft.AspNet.Mvc.Razor.Compilation
|
|||
{
|
||||
public class CompilerCacheTest
|
||||
{
|
||||
private const string ViewPath = "view-path";
|
||||
private const string ViewPath = "Views/Home/Index.cshtml";
|
||||
private const string PrecompiledViewsPath = "Views/Home/Precompiled.cshtml";
|
||||
private readonly IAssemblyLoadContext TestLoadContext = Mock.Of<IAssemblyLoadContext>();
|
||||
|
||||
public static TheoryData ViewImportsPaths =>
|
||||
new TheoryData<string>
|
||||
{
|
||||
Path.Combine("Views", "Home", "_ViewImports.cshtml"),
|
||||
Path.Combine("Views", "_ViewImports.cshtml"),
|
||||
"_ViewImports.cshtml",
|
||||
};
|
||||
|
||||
[Fact]
|
||||
public void GetOrAdd_ReturnsFileNotFoundResult_IfFileIsNotFoundInFileSystem()
|
||||
{
|
||||
// Arrange
|
||||
var fileProvider = new TestFileProvider();
|
||||
var cache = new CompilerCache(Enumerable.Empty<RazorFileInfoCollection>(), TestLoadContext, fileProvider);
|
||||
var type = GetType();
|
||||
|
||||
// Act
|
||||
var result = cache.GetOrAdd("/some/path", _ => { throw new Exception("Shouldn't be called"); });
|
||||
var result = cache.GetOrAdd("/some/path", ThrowsIfCalled);
|
||||
|
||||
// Assert
|
||||
Assert.Same(CompilerCacheResult.FileNotFound, result);
|
||||
|
|
@ -46,7 +49,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Compilation
|
|||
var fileProvider = new TestFileProvider();
|
||||
fileProvider.AddFile(ViewPath, "some content");
|
||||
var cache = new CompilerCache(Enumerable.Empty<RazorFileInfoCollection>(), TestLoadContext, fileProvider);
|
||||
var type = GetType();
|
||||
var type = typeof(TestView);
|
||||
var expected = UncachedCompilationResult.Successful(type, "hello world");
|
||||
|
||||
// Act
|
||||
|
|
@ -68,7 +71,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Compilation
|
|||
var fileProvider = new TestFileProvider();
|
||||
fileProvider.AddFile(ViewPath, "some content");
|
||||
var cache = new CompilerCache(Enumerable.Empty<RazorFileInfoCollection>(), TestLoadContext, fileProvider);
|
||||
var type = typeof(RuntimeCompileIdentical);
|
||||
var type = typeof(TestView);
|
||||
var expected = UncachedCompilationResult.Successful(type, "hello world");
|
||||
|
||||
// Act 1
|
||||
|
|
@ -82,7 +85,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Compilation
|
|||
// Delete the file from the file system and set it's expiration trigger.
|
||||
fileProvider.DeleteFile(ViewPath);
|
||||
fileProvider.GetTrigger(ViewPath).IsExpired = true;
|
||||
var result2 = cache.GetOrAdd(ViewPath, _ => { throw new Exception("shouldn't be called."); });
|
||||
var result2 = cache.GetOrAdd(ViewPath, ThrowsIfCalled);
|
||||
|
||||
// Assert 2
|
||||
Assert.Same(CompilerCacheResult.FileNotFound, result2);
|
||||
|
|
@ -96,9 +99,8 @@ namespace Microsoft.AspNet.Mvc.Razor.Compilation
|
|||
var fileProvider = new TestFileProvider();
|
||||
fileProvider.AddFile(ViewPath, "some content");
|
||||
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");
|
||||
var expected1 = UncachedCompilationResult.Successful(typeof(TestView), "hello world");
|
||||
var expected2 = UncachedCompilationResult.Successful(typeof(DifferentView), "different content");
|
||||
|
||||
// Act 1
|
||||
var result1 = cache.GetOrAdd(ViewPath, _ => expected1);
|
||||
|
|
@ -108,12 +110,55 @@ namespace Microsoft.AspNet.Mvc.Razor.Compilation
|
|||
Assert.Same(expected1, result1.CompilationResult);
|
||||
|
||||
// Act 2
|
||||
fileProvider.GetTrigger(ViewPath).IsExpired = true;
|
||||
var result2 = cache.GetOrAdd(ViewPath, _ => expected2);
|
||||
// Verify we're getting cached results.
|
||||
var result2 = cache.GetOrAdd(ViewPath, ThrowsIfCalled);
|
||||
|
||||
// Assert 2
|
||||
Assert.NotSame(CompilerCacheResult.FileNotFound, result2);
|
||||
Assert.Same(expected2, result2.CompilationResult);
|
||||
Assert.Same(expected1.CompiledType, result2.CompilationResult.CompiledType);
|
||||
|
||||
// Act 3
|
||||
fileProvider.GetTrigger(ViewPath).IsExpired = true;
|
||||
var result3 = cache.GetOrAdd(ViewPath, _ => expected2);
|
||||
|
||||
// Assert 3
|
||||
Assert.NotSame(CompilerCacheResult.FileNotFound, result3);
|
||||
Assert.Same(expected2, result3.CompilationResult);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(ViewImportsPaths))]
|
||||
public void GetOrAdd_ReturnsNewResult_IfAncestorViewImportsWereModified(string globalImportPath)
|
||||
{
|
||||
// Arrange
|
||||
var fileProvider = new TestFileProvider();
|
||||
fileProvider.AddFile(ViewPath, "some content");
|
||||
var cache = new CompilerCache(Enumerable.Empty<RazorFileInfoCollection>(), TestLoadContext, fileProvider);
|
||||
var expected1 = UncachedCompilationResult.Successful(typeof(TestView), "hello world");
|
||||
var expected2 = UncachedCompilationResult.Successful(typeof(DifferentView), "different content");
|
||||
|
||||
// Act 1
|
||||
var result1 = cache.GetOrAdd(ViewPath, _ => expected1);
|
||||
|
||||
// Assert 1
|
||||
Assert.NotSame(CompilerCacheResult.FileNotFound, result1);
|
||||
Assert.Same(expected1, result1.CompilationResult);
|
||||
|
||||
// Act 2
|
||||
// Verify we're getting cached results.
|
||||
var result2 = cache.GetOrAdd(ViewPath, ThrowsIfCalled);
|
||||
|
||||
// Assert 2
|
||||
Assert.NotSame(CompilerCacheResult.FileNotFound, result2);
|
||||
Assert.Same(expected1.CompiledType, result2.CompilationResult.CompiledType);
|
||||
|
||||
// Act 3
|
||||
fileProvider.GetTrigger(globalImportPath).IsExpired = true;
|
||||
var result3 = cache.GetOrAdd(ViewPath, _ => expected2);
|
||||
|
||||
// Assert 2
|
||||
Assert.NotSame(CompilerCacheResult.FileNotFound, result3);
|
||||
Assert.Same(expected2, result3.CompilationResult);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -124,7 +169,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Compilation
|
|||
var fileProvider = mockFileProvider.Object;
|
||||
fileProvider.AddFile(ViewPath, "some content");
|
||||
var cache = new CompilerCache(Enumerable.Empty<RazorFileInfoCollection>(), TestLoadContext, fileProvider);
|
||||
var type = typeof(RuntimeCompileIdentical);
|
||||
var type = typeof(TestView);
|
||||
var expected = UncachedCompilationResult.Successful(type, "hello world");
|
||||
|
||||
// Act 1
|
||||
|
|
@ -135,7 +180,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Compilation
|
|||
Assert.Same(expected, result1.CompilationResult);
|
||||
|
||||
// Act 2
|
||||
var result2 = cache.GetOrAdd(ViewPath, _ => { throw new Exception("shouldn't be called"); });
|
||||
var result2 = cache.GetOrAdd(ViewPath, ThrowsIfCalled);
|
||||
|
||||
// Assert 2
|
||||
Assert.NotSame(CompilerCacheResult.FileNotFound, result2);
|
||||
|
|
@ -144,543 +189,120 @@ namespace Microsoft.AspNet.Mvc.Razor.Compilation
|
|||
mockFileProvider.Verify(v => v.GetFileInfo(ViewPath), Times.Once());
|
||||
}
|
||||
|
||||
private abstract class View
|
||||
[Fact]
|
||||
public void GetOrAdd_UsesViewsSpecifiedFromRazorFileInfoCollection()
|
||||
{
|
||||
public abstract string Content { get; }
|
||||
// Arrange
|
||||
var fileProvider = new TestFileProvider();
|
||||
var cache = new CompilerCache(new[] { new TestViewCollection() }, TestLoadContext, fileProvider);
|
||||
|
||||
// Act
|
||||
var result = cache.GetOrAdd(PrecompiledViewsPath, ThrowsIfCalled);
|
||||
|
||||
// Assert
|
||||
Assert.NotSame(CompilerCacheResult.FileNotFound, result);
|
||||
Assert.Same(typeof(PreCompile), result.CompilationResult.CompiledType);
|
||||
}
|
||||
|
||||
private class PreCompile : View
|
||||
[Fact]
|
||||
public void GetOrAdd_DoesNotRecompile_IfFileTriggerWasSetForPrecompiledFile()
|
||||
{
|
||||
public override string Content { get { return "Hello World it's @DateTime.Now"; } }
|
||||
}
|
||||
// Arrange
|
||||
var fileProvider = new TestFileProvider();
|
||||
var cache = new CompilerCache(new[] { new TestViewCollection() }, TestLoadContext, fileProvider);
|
||||
|
||||
private class RuntimeCompileIdentical : View
|
||||
{
|
||||
public override string Content { get { return new PreCompile().Content; } }
|
||||
}
|
||||
// Act
|
||||
fileProvider.Watch(PrecompiledViewsPath);
|
||||
fileProvider.GetTrigger(PrecompiledViewsPath).IsExpired = true;
|
||||
var result = cache.GetOrAdd(PrecompiledViewsPath, ThrowsIfCalled);
|
||||
|
||||
private class RuntimeCompileDifferent : View
|
||||
{
|
||||
public override string Content { get { return new PreCompile().Content.Substring(1) + " "; } }
|
||||
}
|
||||
|
||||
private class RuntimeCompileDifferentLength : View
|
||||
{
|
||||
public override string Content
|
||||
{
|
||||
get
|
||||
{
|
||||
return new PreCompile().Content + " longer because it was modified at runtime";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Stream GetMemoryStream(string content)
|
||||
{
|
||||
var bytes = Encoding.UTF8.GetBytes(content);
|
||||
|
||||
return new MemoryStream(bytes);
|
||||
// Assert
|
||||
Assert.NotSame(CompilerCacheResult.FileNotFound, result);
|
||||
Assert.Same(typeof(PreCompile), result.CompilationResult.CompiledType);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(10000)]
|
||||
[InlineData(11000)]
|
||||
public void GetOrAdd_UsesFilesFromCache_IfTimestampDiffers_ButContentAndLengthAreTheSame(long fileTimeUTC)
|
||||
[MemberData(nameof(ViewImportsPaths))]
|
||||
public void GetOrAdd_DoesNotRecompile_IfFileTriggerWasSetForViewImports(string globalImportPath)
|
||||
{
|
||||
// Arrange
|
||||
var instance = new RuntimeCompileIdentical();
|
||||
var length = Encoding.UTF8.GetByteCount(instance.Content);
|
||||
var fileProvider = new TestFileProvider();
|
||||
var cache = new CompilerCache(new[] { new ViewCollection() }, TestLoadContext, fileProvider);
|
||||
var fileInfo = new TestFileInfo
|
||||
{
|
||||
Length = length,
|
||||
LastModified = DateTime.FromFileTimeUtc(fileTimeUTC),
|
||||
Content = instance.Content
|
||||
};
|
||||
fileProvider.AddFile(ViewPath, fileInfo);
|
||||
var cache = new CompilerCache(new[] { new TestViewCollection() }, TestLoadContext, fileProvider);
|
||||
|
||||
// Act
|
||||
var result = cache.GetOrAdd(ViewPath,
|
||||
compile: _ => { throw new Exception("Shouldn't be called."); });
|
||||
fileProvider.Watch(globalImportPath);
|
||||
fileProvider.GetTrigger(globalImportPath).IsExpired = true;
|
||||
var result = cache.GetOrAdd(PrecompiledViewsPath, ThrowsIfCalled);
|
||||
|
||||
// Assert
|
||||
Assert.NotSame(CompilerCacheResult.FileNotFound, result);
|
||||
var actual = result.CompilationResult;
|
||||
Assert.NotNull(actual);
|
||||
Assert.Equal(typeof(PreCompile), actual.CompiledType);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(typeof(RuntimeCompileDifferent), 11000)]
|
||||
[InlineData(typeof(RuntimeCompileDifferentLength), 10000)]
|
||||
[InlineData(typeof(RuntimeCompileDifferentLength), 11000)]
|
||||
public void GetOrAdd_RecompilesFile_IfContentAndLengthAreChanged(
|
||||
Type resultViewType,
|
||||
long fileTimeUTC)
|
||||
{
|
||||
// Arrange
|
||||
var instance = (View)Activator.CreateInstance(resultViewType);
|
||||
var length = Encoding.UTF8.GetByteCount(instance.Content);
|
||||
var fileProvider = new TestFileProvider();
|
||||
var cache = new CompilerCache(new[] { new ViewCollection() }, TestLoadContext, fileProvider);
|
||||
|
||||
var fileInfo = new TestFileInfo
|
||||
{
|
||||
Length = length,
|
||||
LastModified = DateTime.FromFileTimeUtc(fileTimeUTC),
|
||||
Content = instance.Content
|
||||
};
|
||||
fileProvider.AddFile(ViewPath, fileInfo);
|
||||
|
||||
// Act
|
||||
var result = cache.GetOrAdd(ViewPath,
|
||||
compile: _ => CompilationResult.Successful(resultViewType));
|
||||
|
||||
// Assert
|
||||
Assert.NotSame(CompilerCacheResult.FileNotFound, result);
|
||||
var actual = result.CompilationResult;
|
||||
Assert.NotNull(actual);
|
||||
Assert.Equal(resultViewType, actual.CompiledType);
|
||||
Assert.Same(typeof(PreCompile), result.CompilationResult.CompiledType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetOrAdd_UsesValueFromCache_IfGlobalHasNotChanged()
|
||||
public void GetOrAdd_ReturnsRuntimeCompiledAndPrecompiledViews()
|
||||
{
|
||||
// Arrange
|
||||
var instance = new PreCompile();
|
||||
var length = Encoding.UTF8.GetByteCount(instance.Content);
|
||||
var fileProvider = new TestFileProvider();
|
||||
|
||||
var lastModified = DateTime.UtcNow;
|
||||
|
||||
var fileInfo = new TestFileInfo
|
||||
{
|
||||
Length = length,
|
||||
LastModified = lastModified,
|
||||
Content = instance.Content
|
||||
};
|
||||
fileProvider.AddFile(ViewPath, fileInfo);
|
||||
|
||||
var globalContent = "global-content";
|
||||
var globalFileInfo = new TestFileInfo
|
||||
{
|
||||
Content = globalContent,
|
||||
LastModified = DateTime.UtcNow
|
||||
};
|
||||
fileProvider.AddFile("_ViewImports.cshtml", globalFileInfo);
|
||||
var globalRazorFileInfo = new RazorFileInfo
|
||||
{
|
||||
Hash = Crc32.Calculate(GetMemoryStream(globalContent)).ToString(CultureInfo.InvariantCulture),
|
||||
HashAlgorithmVersion = 1,
|
||||
LastModified = globalFileInfo.LastModified,
|
||||
Length = globalFileInfo.Length,
|
||||
RelativePath = "_ViewImports.cshtml",
|
||||
FullTypeName = typeof(RuntimeCompileIdentical).FullName
|
||||
};
|
||||
var precompiledViews = new ViewCollection();
|
||||
precompiledViews.Add(globalRazorFileInfo);
|
||||
var cache = new CompilerCache(new[] { precompiledViews }, TestLoadContext, fileProvider);
|
||||
|
||||
// Act
|
||||
var result = cache.GetOrAdd(ViewPath,
|
||||
compile: _ => { throw new Exception("shouldn't be invoked"); });
|
||||
|
||||
// Assert
|
||||
Assert.NotSame(CompilerCacheResult.FileNotFound, result);
|
||||
var actual = result.CompilationResult;
|
||||
Assert.NotNull(actual);
|
||||
Assert.Equal(typeof(PreCompile), actual.CompiledType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetOrAdd_ReturnsFileNotFoundResult_IfPrecompiledViewWasRemovedFromFileSystem()
|
||||
{
|
||||
// Arrange
|
||||
var precompiledViews = new ViewCollection();
|
||||
var fileProvider = new TestFileProvider();
|
||||
var cache = new CompilerCache(new[] { precompiledViews }, TestLoadContext, fileProvider);
|
||||
|
||||
// Act
|
||||
var result = cache.GetOrAdd(ViewPath,
|
||||
compile: _ => { throw new Exception("shouldn't be invoked"); });
|
||||
|
||||
// Assert
|
||||
Assert.Same(CompilerCacheResult.FileNotFound, result);
|
||||
Assert.Null(result.CompilationResult);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetOrAdd_DoesNotReadFileFromFileSystemAfterPrecompiledViewIsVerified()
|
||||
{
|
||||
// Arrange
|
||||
var precompiledViews = new ViewCollection();
|
||||
var mockFileProvider = new Mock<TestFileProvider> { CallBase = true };
|
||||
var fileProvider = mockFileProvider.Object;
|
||||
var precompiledView = precompiledViews.FileInfos[0];
|
||||
var fileInfo = new TestFileInfo
|
||||
{
|
||||
Length = precompiledView.Length,
|
||||
LastModified = precompiledView.LastModified,
|
||||
};
|
||||
fileProvider.AddFile(ViewPath, fileInfo);
|
||||
var cache = new CompilerCache(new[] { precompiledViews }, TestLoadContext, fileProvider);
|
||||
fileProvider.AddFile(ViewPath, "some content");
|
||||
var cache = new CompilerCache(new[] { new TestViewCollection() }, TestLoadContext, fileProvider);
|
||||
var expected = CompilationResult.Successful(typeof(TestView));
|
||||
|
||||
// Act 1
|
||||
var result1 = cache.GetOrAdd(ViewPath,
|
||||
compile: _ => { throw new Exception("shouldn't be invoked"); });
|
||||
var result1 = cache.GetOrAdd(ViewPath, _ => expected);
|
||||
|
||||
// Assert 1
|
||||
Assert.NotSame(CompilerCacheResult.FileNotFound, result1);
|
||||
var actual1 = result1.CompilationResult;
|
||||
Assert.NotNull(actual1);
|
||||
Assert.Equal(typeof(PreCompile), actual1.CompiledType);
|
||||
mockFileProvider.Verify(v => v.GetFileInfo(ViewPath), Times.Once());
|
||||
Assert.Same(expected, result1.CompilationResult);
|
||||
|
||||
// Act 2
|
||||
var result2 = cache.GetOrAdd(ViewPath,
|
||||
compile: _ => { throw new Exception("shouldn't be invoked"); });
|
||||
var result2 = cache.GetOrAdd(ViewPath, ThrowsIfCalled);
|
||||
|
||||
// Assert 2
|
||||
Assert.NotSame(CompilerCacheResult.FileNotFound, result2);
|
||||
var actual2 = result2.CompilationResult;
|
||||
Assert.NotNull(actual2);
|
||||
Assert.Equal(typeof(PreCompile), actual2.CompiledType);
|
||||
mockFileProvider.Verify(v => v.GetFileInfo(ViewPath), Times.Once());
|
||||
}
|
||||
Assert.Same(typeof(TestView), result2.CompilationResult.CompiledType);
|
||||
|
||||
[ConditionalTheory]
|
||||
// Skipping for now since this is going to change.
|
||||
[FrameworkSkipCondition(RuntimeFrameworks.Mono)]
|
||||
public void GetOrAdd_IgnoresCachedValueIfFileIsIdentical_ButViewImportsWasAdedSinceTheCacheWasCreated()
|
||||
{
|
||||
// Arrange
|
||||
var expectedType = typeof(RuntimeCompileDifferent);
|
||||
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 }, TestLoadContext, fileProvider);
|
||||
var testFile = new TestFileInfo
|
||||
{
|
||||
Content = new PreCompile().Content,
|
||||
LastModified = precompiledFile.LastModified,
|
||||
PhysicalPath = precompiledFile.RelativePath
|
||||
};
|
||||
fileProvider.AddFile(precompiledFile.RelativePath, testFile);
|
||||
var relativeFile = new RelativeFileInfo(testFile, testFile.PhysicalPath);
|
||||
// Act 3
|
||||
var result3 = cache.GetOrAdd(PrecompiledViewsPath, ThrowsIfCalled);
|
||||
|
||||
// Act 1
|
||||
var result1 = cache.GetOrAdd(testFile.PhysicalPath,
|
||||
compile: _ => { throw new Exception("should not be called"); });
|
||||
|
||||
// Assert 1
|
||||
Assert.NotSame(CompilerCacheResult.FileNotFound, result1);
|
||||
var actual1 = result1.CompilationResult;
|
||||
Assert.NotNull(actual1);
|
||||
Assert.Equal(typeof(PreCompile), actual1.CompiledType);
|
||||
|
||||
// Act 2
|
||||
var globalTrigger = fileProvider.GetTrigger("Views\\_ViewImports.cshtml");
|
||||
globalTrigger.IsExpired = true;
|
||||
var result2 = cache.GetOrAdd(testFile.PhysicalPath,
|
||||
compile: _ => CompilationResult.Successful(expectedType));
|
||||
|
||||
// Assert 2
|
||||
// Assert 3
|
||||
Assert.NotSame(CompilerCacheResult.FileNotFound, result2);
|
||||
var actual2 = result2.CompilationResult;
|
||||
Assert.NotNull(actual2);
|
||||
Assert.Equal(expectedType, actual2.CompiledType);
|
||||
Assert.Same(typeof(PreCompile), result3.CompilationResult.CompiledType);
|
||||
}
|
||||
|
||||
[ConditionalTheory]
|
||||
// Skipping for now since this is going to change.
|
||||
[FrameworkSkipCondition(RuntimeFrameworks.Mono)]
|
||||
public void GetOrAdd_IgnoresCachedValueIfFileIsIdentical_ButGlobalWasDeletedSinceCacheWasCreated()
|
||||
private class TestView
|
||||
{
|
||||
// Arrange
|
||||
var expectedType = typeof(RuntimeCompileDifferent);
|
||||
var lastModified = DateTime.UtcNow;
|
||||
var fileProvider = new TestFileProvider();
|
||||
|
||||
var viewCollection = new ViewCollection();
|
||||
var precompiledView = viewCollection.FileInfos[0];
|
||||
precompiledView.RelativePath = "Views\\Index.cshtml";
|
||||
var viewFileInfo = new TestFileInfo
|
||||
{
|
||||
Content = new PreCompile().Content,
|
||||
LastModified = precompiledView.LastModified,
|
||||
PhysicalPath = precompiledView.RelativePath
|
||||
};
|
||||
fileProvider.AddFile(viewFileInfo.PhysicalPath, viewFileInfo);
|
||||
|
||||
var globalFileInfo = new TestFileInfo
|
||||
{
|
||||
PhysicalPath = "Views\\_ViewImports.cshtml",
|
||||
Content = "viewstart-content",
|
||||
LastModified = lastModified
|
||||
};
|
||||
var globalFile = new RazorFileInfo
|
||||
{
|
||||
FullTypeName = typeof(RuntimeCompileIdentical).FullName,
|
||||
RelativePath = globalFileInfo.PhysicalPath,
|
||||
LastModified = globalFileInfo.LastModified,
|
||||
Hash = RazorFileHash.GetHash(globalFileInfo, hashAlgorithmVersion: 1),
|
||||
HashAlgorithmVersion = 1,
|
||||
Length = globalFileInfo.Length
|
||||
};
|
||||
fileProvider.AddFile(globalFileInfo.PhysicalPath, globalFileInfo);
|
||||
|
||||
viewCollection.Add(globalFile);
|
||||
var cache = new CompilerCache(new[] { viewCollection }, TestLoadContext, fileProvider);
|
||||
|
||||
// Act 1
|
||||
var result1 = cache.GetOrAdd(viewFileInfo.PhysicalPath,
|
||||
compile: _ => { throw new Exception("should not be called"); });
|
||||
|
||||
// Assert 1
|
||||
Assert.NotSame(CompilerCacheResult.FileNotFound, result1);
|
||||
var actual1 = result1.CompilationResult;
|
||||
Assert.NotNull(actual1);
|
||||
Assert.Equal(typeof(PreCompile), actual1.CompiledType);
|
||||
|
||||
// Act 2
|
||||
var trigger = fileProvider.GetTrigger(globalFileInfo.PhysicalPath);
|
||||
trigger.IsExpired = true;
|
||||
var result2 = cache.GetOrAdd(viewFileInfo.PhysicalPath,
|
||||
compile: _ => CompilationResult.Successful(expectedType));
|
||||
|
||||
// Assert 2
|
||||
Assert.NotSame(CompilerCacheResult.FileNotFound, result2);
|
||||
var actual2 = result2.CompilationResult;
|
||||
Assert.NotNull(actual2);
|
||||
Assert.Equal(expectedType, actual2.CompiledType);
|
||||
}
|
||||
|
||||
public static IEnumerable<object[]> GetOrAdd_IgnoresCachedValue_IfGlobalWasChangedSinceCacheWasCreatedData
|
||||
private class PreCompile
|
||||
{
|
||||
get
|
||||
{
|
||||
var globalContent = "global-content";
|
||||
var contentStream = GetMemoryStream(globalContent);
|
||||
var lastModified = DateTime.UtcNow;
|
||||
int length = Encoding.UTF8.GetByteCount(globalContent);
|
||||
var path = "Views\\_ViewImports.cshtml";
|
||||
}
|
||||
|
||||
var razorFileInfo = new RazorFileInfo
|
||||
public class DifferentView
|
||||
{
|
||||
}
|
||||
|
||||
private CompilationResult ThrowsIfCalled(RelativeFileInfo file)
|
||||
{
|
||||
throw new Exception("Shouldn't be called");
|
||||
}
|
||||
|
||||
private class TestViewCollection : RazorFileInfoCollection
|
||||
{
|
||||
public TestViewCollection()
|
||||
{
|
||||
FileInfos = new List<RazorFileInfo>
|
||||
{
|
||||
Hash = Crc32.Calculate(contentStream).ToString(CultureInfo.InvariantCulture),
|
||||
HashAlgorithmVersion = 1,
|
||||
LastModified = lastModified,
|
||||
Length = length,
|
||||
RelativePath = path
|
||||
new RazorFileInfo
|
||||
{
|
||||
FullTypeName = typeof(PreCompile).FullName,
|
||||
RelativePath = PrecompiledViewsPath,
|
||||
}
|
||||
};
|
||||
|
||||
// Length does not match
|
||||
var testFileInfo1 = new TestFileInfo
|
||||
{
|
||||
Length = 7732
|
||||
};
|
||||
|
||||
yield return new object[] { razorFileInfo, testFileInfo1 };
|
||||
|
||||
// Content and last modified do not match
|
||||
var testFileInfo2 = new TestFileInfo
|
||||
{
|
||||
Length = length,
|
||||
Content = "viewstart-modified",
|
||||
LastModified = lastModified.AddSeconds(100),
|
||||
};
|
||||
|
||||
yield return new object[] { razorFileInfo, testFileInfo2 };
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(GetOrAdd_IgnoresCachedValue_IfGlobalWasChangedSinceCacheWasCreatedData))]
|
||||
public void GetOrAdd_IgnoresCachedValue_IfGlobalFileWasChangedSinceCacheWasCreated(
|
||||
RazorFileInfo viewStartRazorFileInfo, IFileInfo globalFileInfo)
|
||||
{
|
||||
// Arrange
|
||||
var expectedType = typeof(RuntimeCompileDifferent);
|
||||
var lastModified = DateTime.UtcNow;
|
||||
var viewStartLastModified = DateTime.UtcNow;
|
||||
var content = "some content";
|
||||
var fileInfo = new TestFileInfo
|
||||
{
|
||||
Length = 1020,
|
||||
Content = content,
|
||||
LastModified = lastModified,
|
||||
PhysicalPath = "Views\\home\\index.cshtml"
|
||||
};
|
||||
|
||||
var fileProvider = new TestFileProvider();
|
||||
fileProvider.AddFile(fileInfo.PhysicalPath, fileInfo);
|
||||
fileProvider.AddFile(viewStartRazorFileInfo.RelativePath, globalFileInfo);
|
||||
var viewCollection = new ViewCollection();
|
||||
var cache = new CompilerCache(new[] { viewCollection }, TestLoadContext, fileProvider);
|
||||
|
||||
// Act
|
||||
var result = cache.GetOrAdd(fileInfo.PhysicalPath,
|
||||
compile: _ => CompilationResult.Successful(expectedType));
|
||||
|
||||
// Assert
|
||||
Assert.NotSame(CompilerCacheResult.FileNotFound, result);
|
||||
var actual = result.CompilationResult;
|
||||
Assert.NotNull(actual);
|
||||
Assert.Equal(expectedType, actual.CompiledType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetOrAdd_DoesNotCacheCompiledContent_OnCallsAfterInitial()
|
||||
{
|
||||
// Arrange
|
||||
var lastModified = DateTime.UtcNow;
|
||||
var fileProvider = new TestFileProvider();
|
||||
var cache = new CompilerCache(Enumerable.Empty<RazorFileInfoCollection>(), TestLoadContext, fileProvider);
|
||||
var fileInfo = new TestFileInfo
|
||||
{
|
||||
PhysicalPath = "test",
|
||||
LastModified = lastModified
|
||||
};
|
||||
fileProvider.AddFile("test", fileInfo);
|
||||
var type = GetType();
|
||||
var uncachedResult = UncachedCompilationResult.Successful(type, "hello world");
|
||||
|
||||
// Act
|
||||
cache.GetOrAdd("test", _ => uncachedResult);
|
||||
var result1 = cache.GetOrAdd("test", _ => { throw new Exception("shouldn't be called."); });
|
||||
var result2 = cache.GetOrAdd("test", _ => { throw new Exception("shouldn't be called."); });
|
||||
|
||||
// Assert
|
||||
Assert.NotSame(CompilerCacheResult.FileNotFound, result1);
|
||||
Assert.NotSame(CompilerCacheResult.FileNotFound, result2);
|
||||
|
||||
var actual1 = Assert.IsType<CompilationResult>(result1.CompilationResult);
|
||||
var actual2 = Assert.IsType<CompilationResult>(result2.CompilationResult);
|
||||
Assert.NotSame(uncachedResult, actual1);
|
||||
Assert.NotSame(uncachedResult, actual2);
|
||||
Assert.Same(type, actual1.CompiledType);
|
||||
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;
|
||||
return typeof(TestViewCollection).Assembly;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,87 @@
|
|||
// Copyright (c) .NET Foundation. 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.AspNet.Mvc.Razor.Precompilation;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.Framework.DependencyInjection
|
||||
{
|
||||
public class MvcRazorMvcBuilderExtensionsTest
|
||||
{
|
||||
[Fact]
|
||||
public void IsValidRazorFileInfoCollection_ReturnsFalse_IfTypeIsAbstract()
|
||||
{
|
||||
// Arrange
|
||||
var type = typeof(AbstractRazorFileInfoCollection);
|
||||
|
||||
// Act
|
||||
var result = MvcRazorMvcBuilderExtensions.IsValidRazorFileInfoCollection(type);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsValidRazorFileInfoCollection_ReturnsFalse_IfTypeHasGenericParameters()
|
||||
{
|
||||
// Arrange
|
||||
var type = typeof(GenericRazorFileInfoCollection<>);
|
||||
|
||||
// Act
|
||||
var result = MvcRazorMvcBuilderExtensions.IsValidRazorFileInfoCollection(type);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsValidRazorFileInfoCollection_ReturnsFalse_IfTypeDoesNotDeriveFromRazorFileInfoCollection()
|
||||
{
|
||||
// Arrange
|
||||
var type = typeof(NonSubTypeRazorFileInfoCollection);
|
||||
|
||||
// Act
|
||||
var result = MvcRazorMvcBuilderExtensions.IsValidRazorFileInfoCollection(type);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(typeof(ParameterConstructorRazorFileInfoCollection))]
|
||||
[InlineData(typeof(ViewCollection))]
|
||||
public void IsValidRazorFileInfoCollection_ReturnsTrue_IfTypeDerivesFromRazorFileInfoCollection(Type type)
|
||||
{
|
||||
// Act
|
||||
var result = MvcRazorMvcBuilderExtensions.IsValidRazorFileInfoCollection(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
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Reflection;
|
||||
using Microsoft.AspNet.Builder;
|
||||
using Microsoft.Framework.DependencyInjection;
|
||||
|
||||
|
|
@ -12,7 +13,9 @@ namespace PrecompilationWebSite
|
|||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
// Add MVC services to the services container
|
||||
services.AddMvc();
|
||||
services
|
||||
.AddMvc()
|
||||
.AddPrecompiledRazorViews(GetType().GetTypeInfo().Assembly);
|
||||
}
|
||||
|
||||
public void Configure(IApplicationBuilder app)
|
||||
|
|
|
|||
|
|
@ -1,21 +1,22 @@
|
|||
// Copyright (c) .NET Foundation. 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;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNet.Mvc.Razor;
|
||||
using Microsoft.AspNet.Mvc.Razor.Compilation;
|
||||
using Microsoft.Framework.OptionsModel;
|
||||
using Microsoft.AspNet.Mvc.Razor.Precompilation;
|
||||
using Microsoft.Dnx.Runtime;
|
||||
using Microsoft.Framework.OptionsModel;
|
||||
|
||||
namespace RazorCompilerCacheWebSite
|
||||
{
|
||||
public class CustomCompilerCache : CompilerCache
|
||||
{
|
||||
public CustomCompilerCache(IAssemblyProvider assemblyProvider,
|
||||
public CustomCompilerCache(IEnumerable<RazorFileInfoCollection> fileInfoCollection,
|
||||
IAssemblyLoadContextAccessor loadContextAccessor,
|
||||
IOptions<RazorViewEngineOptions> optionsAccessor,
|
||||
CompilerCacheInitialiedService cacheInitializedService)
|
||||
: base(assemblyProvider, loadContextAccessor, optionsAccessor)
|
||||
: base(fileInfoCollection, loadContextAccessor, optionsAccessor)
|
||||
{
|
||||
cacheInitializedService.Initialized = true;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue