diff --git a/src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilerCache.cs b/src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilerCache.cs
index 966b9effca..2220c1fe58 100644
--- a/src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilerCache.cs
+++ b/src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilerCache.cs
@@ -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
{
///
- /// 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.
///
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 populated with precompiled views
/// discovered using .
///
- /// The that provides assemblies
- /// for precompiled view discovery.
+ /// The sequence of that provides
+ /// information for precompiled view discovery.
/// The .
/// An accessor to the .
- public CompilerCache(IAssemblyProvider assemblyProvider,
- IAssemblyLoadContextAccessor loadContextAccessor,
- IOptions optionsAccessor)
- : this(GetFileInfos(assemblyProvider.CandidateAssemblies),
- loadContextAccessor.GetLoadContext(RazorFileInfoCollectionType.Assembly),
+ public CompilerCache(
+ IEnumerable razorFileInfoCollections,
+ IAssemblyLoadContextAccessor loadContextAccessor,
+ IOptions optionsAccessor)
+ : this(razorFileInfoCollections,
+ loadContextAccessor.GetLoadContext(RazorHostAssembly),
optionsAccessor.Options.FileProvider)
{
}
- internal CompilerCache(IEnumerable razorFileInfoCollections,
- IAssemblyLoadContext loadContext,
- IFileProvider fileProvider)
+ internal CompilerCache(
+ IEnumerable razorFileInfoCollections,
+ IAssemblyLoadContext loadContext,
+ IFileProvider fileProvider)
{
_fileProvider = fileProvider;
_cache = new MemoryCache(new MemoryCacheOptions { CompactOnMemoryPressure = false });
- var cacheEntries = new List();
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(location);
- if (globalFileEntry != null)
- {
- // Add the composite _ViewImports entry as a dependency.
- entry.AssociatedGlobalFileEntry = globalFileEntry;
- break;
- }
+ _cache.Set(normalizedPath, cacheEntry);
}
}
}
///
- public CompilerCacheResult GetOrAdd([NotNull] string relativePath,
- [NotNull] Func compile)
- {
- var result = GetOrAddCore(relativePath, compile);
- if (result == null)
- {
- return CompilerCacheResult.FileNotFound;
- }
-
- return new CompilerCacheResult(result.CompilationResult);
- }
-
- private GetOrAddResult GetOrAddCore(string relativePath,
- Func compile)
+ public CompilerCacheResult GetOrAdd(
+ [NotNull] string relativePath,
+ [NotNull] Func compile)
{
var normalizedPath = NormalizePath(relativePath);
- var cacheEntry = _cache.Get(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 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(
- 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 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 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 GetFileInfos(IEnumerable 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; }
- }
}
}
diff --git a/src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilerCacheEntry.cs b/src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilerCacheEntry.cs
deleted file mode 100644
index dac6e16a70..0000000000
--- a/src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilerCacheEntry.cs
+++ /dev/null
@@ -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
-{
- ///
- /// An entry in that contain metadata about precompiled and dynamically compiled file.
- ///
- public class CompilerCacheEntry
- {
- ///
- /// Initializes a new instance of for a file that was precompiled.
- ///
- /// Metadata about the precompiled file.
- /// The compiled .
- 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;
- }
-
- ///
- /// Initializes a new instance of for a file that was dynamically compiled.
- ///
- /// Metadata about the file that was compiled.
- /// The compiled .
- public CompilerCacheEntry([NotNull] RelativeFileInfo info, [NotNull] Type compiledType)
- {
- CompiledType = compiledType;
- RelativePath = info.RelativePath;
- Length = info.FileInfo.Length;
- LastModified = info.FileInfo.LastModified;
- }
-
- ///
- /// Gets the produced as a result of compilation.
- ///
- public Type CompiledType { get; }
-
- ///
- /// Gets the path of the compiled file relative to the root of the application.
- ///
- public string RelativePath { get; }
-
- ///
- /// Gets the size of file (in bytes) on disk.
- ///
- public long Length { get; }
-
- ///
- /// Gets or sets the last modified for the file at the time of compilation.
- ///
- public DateTimeOffset LastModified { get; set; }
-
- ///
- /// Gets the file hash, should only be available for pre compiled files.
- ///
- public string Hash { get; }
-
- ///
- /// Gets the version of the hash algorithm used to generate .
- ///
- ///
- /// This value is only initialized for precompiled views.
- ///
- public int HashAlgorithmVersion { get; }
-
- ///
- /// Gets a flag that indicates if the file is precompiled.
- ///
- public bool IsPreCompiled { get; }
-
- ///
- /// Gets or sets the for the nearest _ViewImports that the compiled type
- /// depends on.
- ///
- public CompilerCacheEntry AssociatedGlobalFileEntry { get; set; }
-
- ///
- /// Gets or sets a flag that determines if the validity of this cache entry was performed at runtime.
- ///
- public bool IsValidatedPreCompiled { get; set; }
- }
-}
diff --git a/src/Microsoft.AspNet.Mvc.Razor/DependencyInjection/MvcRazorMvcBuilderExtensions.cs b/src/Microsoft.AspNet.Mvc.Razor/DependencyInjection/MvcRazorMvcBuilderExtensions.cs
index 7124572613..10fb5efc11 100644
--- a/src/Microsoft.AspNet.Mvc.Razor/DependencyInjection/MvcRazorMvcBuilderExtensions.cs
+++ b/src/Microsoft.AspNet.Mvc.Razor/DependencyInjection/MvcRazorMvcBuilderExtensions.cs
@@ -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 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();
}
+
+ private static IEnumerable GetFileInfoCollections(IEnumerable assemblies) =>
+ assemblies
+ .SelectMany(assembly => assembly.ExportedTypes)
+ .Where(IsValidRazorFileInfoCollection)
+ .Select(Activator.CreateInstance)
+ .Cast();
+
+ internal static bool IsValidRazorFileInfoCollection(Type type) =>
+ RazorFileInfoCollectionType.IsAssignableFrom(type) &&
+ !type.GetTypeInfo().IsAbstract &&
+ !type.GetTypeInfo().ContainsGenericParameters;
}
}
diff --git a/src/Microsoft.AspNet.Mvc.Razor/Precompilation/RazorFileInfoCollection.cs b/src/Microsoft.AspNet.Mvc.Razor/Precompilation/RazorFileInfoCollection.cs
index 3ff7e92ca8..438d1d9afa 100644
--- a/src/Microsoft.AspNet.Mvc.Razor/Precompilation/RazorFileInfoCollection.cs
+++ b/src/Microsoft.AspNet.Mvc.Razor/Precompilation/RazorFileInfoCollection.cs
@@ -25,7 +25,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Precompilation
public string SymbolsResourceName { get; protected set; }
///
- /// Gets the of s.
+ /// Gets the of s.
///
public IReadOnlyList FileInfos { get; protected set; }
diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/PrecompilationTest.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/PrecompilationTest.cs
index 23ea26a4c1..c3ded05252 100644
--- a/test/Microsoft.AspNet.Mvc.FunctionalTests/PrecompilationTest.cs
+++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/PrecompilationTest.cs
@@ -41,10 +41,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
var applicationEnvironment = serviceProvider.GetRequiredService();
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();
-
- 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()
diff --git a/test/Microsoft.AspNet.Mvc.Razor.Test/Compilation/CompilerCacheTest.cs b/test/Microsoft.AspNet.Mvc.Razor.Test/Compilation/CompilerCacheTest.cs
index c500050d1d..fc1388ffbd 100644
--- a/test/Microsoft.AspNet.Mvc.Razor.Test/Compilation/CompilerCacheTest.cs
+++ b/test/Microsoft.AspNet.Mvc.Razor.Test/Compilation/CompilerCacheTest.cs
@@ -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();
+ public static TheoryData ViewImportsPaths =>
+ new TheoryData
+ {
+ 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(), 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(), 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(), 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(), 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(), 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(), 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 { 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