diff --git a/src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilationResult.cs b/src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilationResult.cs
index d1582323e7..16318d9b80 100644
--- a/src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilationResult.cs
+++ b/src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilationResult.cs
@@ -10,83 +10,63 @@ namespace Microsoft.AspNet.Mvc.Razor.Compilation
///
/// Represents the result of compilation.
///
- public class CompilationResult
+ public struct CompilationResult
{
///
- /// Creates a new instance of .
- ///
- protected CompilationResult()
- {
- }
-
- ///
- /// Gets (or sets in derived types) the type produced as a result of compilation.
- ///
- /// This property is null when compilation failed.
- public Type CompiledType { get; protected set; }
-
- ///
- /// Gets (or sets in derived types) the generated C# content that was compiled.
- ///
- public string CompiledContent { get; protected set; }
-
- ///
- /// Gets the s produced from parsing or compiling the Razor file.
- ///
- /// This property is null when compilation succeeded. An empty sequence
- /// indicates a failed compilation.
- public IEnumerable CompilationFailures { get; private set; }
-
- ///
- /// Gets the .
- ///
- /// The current instance.
- /// Thrown if compilation failed.
- public CompilationResult EnsureSuccessful()
- {
- if (CompilationFailures != null)
- {
- throw new CompilationFailedException(CompilationFailures);
- }
-
- return this;
- }
-
- ///
- /// Creates a for a failed compilation.
- ///
- /// s produced from parsing or
- /// compiling the Razor file.
- /// A instance for a failed compilation.
- public static CompilationResult Failed(IEnumerable compilationFailures)
- {
- if (compilationFailures == null)
- {
- throw new ArgumentNullException(nameof(compilationFailures));
- }
-
- return new CompilationResult
- {
- CompilationFailures = compilationFailures
- };
- }
-
- ///
- /// Creates a for a successful compilation.
+ /// Initializes a new instance of for a successful compilation.
///
/// The compiled type.
- /// A instance for a successful compilation.
- public static CompilationResult Successful(Type type)
+ public CompilationResult(Type type)
{
if (type == null)
{
throw new ArgumentNullException(nameof(type));
}
- return new CompilationResult
+ CompiledType = type;
+ CompilationFailures = null;
+ }
+
+ ///
+ /// Initializes a new instance of for a failed compilation.
+ ///
+ /// s produced from parsing or
+ /// compiling the Razor file.
+ public CompilationResult(IEnumerable compilationFailures)
+ {
+ if (compilationFailures == null)
{
- CompiledType = type
- };
+ throw new ArgumentNullException(nameof(compilationFailures));
+ }
+
+ CompiledType = null;
+ CompilationFailures = compilationFailures;
+ }
+
+ ///
+ /// Gets the type produced as a result of compilation.
+ ///
+ /// This property is null when compilation failed.
+ public Type CompiledType { get; }
+
+ ///
+ /// Gets the s produced from parsing or compiling the Razor file.
+ ///
+ /// This property is null when compilation succeeded. An empty sequence
+ /// indicates a failed compilation.
+ public IEnumerable CompilationFailures { get; }
+
+ ///
+ /// Gets the .
+ ///
+ /// The current instance.
+ /// Thrown if compilation failed.
+ public void EnsureSuccessful()
+ {
+ if (CompilationFailures != null)
+ {
+ throw new CompilationFailedException(CompilationFailures);
+ }
}
}
}
diff --git a/src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilerCache.cs b/src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilerCache.cs
index 0a44ad1b26..da6bb51cf0 100644
--- a/src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilerCache.cs
+++ b/src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilerCache.cs
@@ -8,6 +8,7 @@ using System.Diagnostics;
using System.Text;
using Microsoft.AspNet.FileProviders;
using Microsoft.Extensions.Caching.Memory;
+using Microsoft.Extensions.Primitives;
namespace Microsoft.AspNet.Mvc.Razor.Compilation
{
@@ -56,7 +57,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Compilation
foreach (var item in precompiledViews)
{
- var cacheEntry = new CompilerCacheResult(CompilationResult.Successful(item.Value));
+ var cacheEntry = new CompilerCacheResult(new CompilationResult(item.Value));
_cache.Set(GetNormalizedPath(item.Key), cacheEntry);
}
}
@@ -95,33 +96,29 @@ namespace Microsoft.AspNet.Mvc.Razor.Compilation
string normalizedPath,
Func compile)
{
- CompilerCacheResult cacheResult;
var fileInfo = _fileProvider.GetFileInfo(normalizedPath);
MemoryCacheEntryOptions cacheEntryOptions;
- CompilerCacheResult cacheResultToCache;
+ CompilerCacheResult cacheResult;
if (!fileInfo.Exists)
{
- cacheResultToCache = CompilerCacheResult.FileNotFound;
- cacheResult = CompilerCacheResult.FileNotFound;
+ var expirationToken = _fileProvider.Watch(normalizedPath);
+ cacheResult = new CompilerCacheResult(new[] { expirationToken });
cacheEntryOptions = new MemoryCacheEntryOptions();
- cacheEntryOptions.AddExpirationToken(_fileProvider.Watch(normalizedPath));
+ cacheEntryOptions.AddExpirationToken(expirationToken);
}
else
{
var relativeFileInfo = new RelativeFileInfo(fileInfo, normalizedPath);
- var compilationResult = compile(relativeFileInfo).EnsureSuccessful();
+ var compilationResult = compile(relativeFileInfo);
+ compilationResult.EnsureSuccessful();
cacheEntryOptions = GetMemoryCacheEntryOptions(normalizedPath);
-
- // 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);
+ cacheResult = new CompilerCacheResult(
+ compilationResult,
+ cacheEntryOptions.ExpirationTokens);
}
- _cache.Set(normalizedPath, cacheResultToCache, cacheEntryOptions);
+ _cache.Set(normalizedPath, cacheResult, cacheEntryOptions);
return cacheResult;
}
diff --git a/src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilerCacheResult.cs b/src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilerCacheResult.cs
index 130ab74c15..2425e33d73 100644
--- a/src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilerCacheResult.cs
+++ b/src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilerCacheResult.cs
@@ -2,46 +2,77 @@
// 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 Microsoft.Extensions.Primitives;
namespace Microsoft.AspNet.Mvc.Razor.Compilation
{
///
/// Result of .
///
- public class CompilerCacheResult
+ public struct CompilerCacheResult
{
- ///
- /// Result of when the specified file does not exist in the
- /// file system.
- ///
- public static CompilerCacheResult FileNotFound { get; } = new CompilerCacheResult();
-
///
/// Initializes a new instance of with the specified
- /// .
+ /// .
///
- /// The
+ /// The .
public CompilerCacheResult(CompilationResult compilationResult)
+ : this(compilationResult, new IChangeToken[0])
{
- if (compilationResult == null)
- {
- throw new ArgumentNullException(nameof(compilationResult));
- }
-
- CompilationResult = compilationResult;
}
///
- /// Initializes a new instance of for a failed file lookup.
+ /// Initializes a new instance of with the specified
+ /// .
///
- protected CompilerCacheResult()
+ /// The .
+ /// One or more instances that indicate when
+ /// this result has expired.
+ public CompilerCacheResult(CompilationResult compilationResult, IList expirationTokens)
{
+ if (expirationTokens == null)
+ {
+ throw new ArgumentNullException(nameof(expirationTokens));
+ }
+
+ CompilationResult = compilationResult;
+ Success = true;
+ ExpirationTokens = expirationTokens;
+ }
+
+ ///
+ /// Initializes a new instance of for a file that could not be
+ /// found in the file system.
+ ///
+ /// One or more instances that indicate when
+ /// this result has expired.
+ public CompilerCacheResult(IList expirationTokens)
+ {
+ if (expirationTokens == null)
+ {
+ throw new ArgumentNullException(nameof(expirationTokens));
+ }
+
+ CompilationResult = default(CompilationResult);
+ Success = false;
+ ExpirationTokens = expirationTokens;
}
///
/// The .
///
- /// This property is null when file lookup failed.
+ /// This property is not available when is false.
public CompilationResult CompilationResult { get; }
+
+ ///
+ /// instances that indicate when this result has expired.
+ ///
+ public IList ExpirationTokens { get; }
+
+ ///
+ /// Gets a value that determines if the view was successfully found and compiled.
+ ///
+ public bool Success { get; }
}
}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Mvc.Razor/Compilation/RazorCompilationService.cs b/src/Microsoft.AspNet.Mvc.Razor/Compilation/RazorCompilationService.cs
index 2b7e89cf83..92cadaa155 100644
--- a/src/Microsoft.AspNet.Mvc.Razor/Compilation/RazorCompilationService.cs
+++ b/src/Microsoft.AspNet.Mvc.Razor/Compilation/RazorCompilationService.cs
@@ -100,7 +100,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Compilation
failures.Add(compilationFailure);
}
- return CompilationResult.Failed(failures);
+ return new CompilationResult(failures);
}
private DiagnosticMessage CreateDiagnosticMessage(RazorError error, string filePath)
diff --git a/src/Microsoft.AspNet.Mvc.Razor/Compilation/RoslynCompilationService.cs b/src/Microsoft.AspNet.Mvc.Razor/Compilation/RoslynCompilationService.cs
index 8f3abe62bb..09b5ae634d 100644
--- a/src/Microsoft.AspNet.Mvc.Razor/Compilation/RoslynCompilationService.cs
+++ b/src/Microsoft.AspNet.Mvc.Razor/Compilation/RoslynCompilationService.cs
@@ -149,7 +149,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Compilation
var type = assembly.GetExportedTypes()
.First(t => t.Name.StartsWith(_classPrefix, StringComparison.Ordinal));
- return UncachedCompilationResult.Successful(type, compilationContent);
+ return new CompilationResult(type);
}
}
}
@@ -214,7 +214,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Compilation
failures.Add(compilationFailure);
}
- return CompilationResult.Failed(failures);
+ return new CompilationResult(failures);
}
private static string GetFilePath(string relativePath, Diagnostic diagnostic)
diff --git a/src/Microsoft.AspNet.Mvc.Razor/Compilation/UncachedCompilationResult.cs b/src/Microsoft.AspNet.Mvc.Razor/Compilation/UncachedCompilationResult.cs
deleted file mode 100644
index 0e96345135..0000000000
--- a/src/Microsoft.AspNet.Mvc.Razor/Compilation/UncachedCompilationResult.cs
+++ /dev/null
@@ -1,47 +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;
-
-namespace Microsoft.AspNet.Mvc.Razor.Compilation
-{
- ///
- /// Represents the result of compilation that does not come from the .
- ///
- public class UncachedCompilationResult : CompilationResult
- {
- private UncachedCompilationResult()
- {
- }
-
- public string RazorFileContent { get; private set; }
-
- ///
- /// Creates a that represents a success in compilation.
- ///
- /// The compiled type.
- /// The generated C# content that was compiled.
- /// An instance that indicates a successful
- /// compilation.
- public static UncachedCompilationResult Successful(
- Type type,
- string compiledContent)
- {
- if (type == null)
- {
- throw new ArgumentNullException(nameof(type));
- }
-
- if (compiledContent == null)
- {
- throw new ArgumentNullException(nameof(compiledContent));
- }
-
- return new UncachedCompilationResult
- {
- CompiledType = type,
- CompiledContent = compiledContent,
- };
- }
- }
-}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Mvc.Razor/VirtualPathRazorPageFactory.cs b/src/Microsoft.AspNet.Mvc.Razor/DefaultRazorPageFactoryProvider.cs
similarity index 58%
rename from src/Microsoft.AspNet.Mvc.Razor/VirtualPathRazorPageFactory.cs
rename to src/Microsoft.AspNet.Mvc.Razor/DefaultRazorPageFactoryProvider.cs
index 93f1d60b22..5d92b0074c 100644
--- a/src/Microsoft.AspNet.Mvc.Razor/VirtualPathRazorPageFactory.cs
+++ b/src/Microsoft.AspNet.Mvc.Razor/DefaultRazorPageFactoryProvider.cs
@@ -7,10 +7,10 @@ using Microsoft.AspNet.Mvc.Razor.Compilation;
namespace Microsoft.AspNet.Mvc.Razor
{
///
- /// Represents a that creates instances
+ /// Represents a that creates instances
/// from razor files in the file system.
///
- public class VirtualPathRazorPageFactory : IRazorPageFactory
+ public class DefaultRazorPageFactoryProvider : IRazorPageFactoryProvider
{
///
/// This delegate holds on to an instance of .
@@ -20,11 +20,11 @@ namespace Microsoft.AspNet.Mvc.Razor
private ICompilerCache _compilerCache;
///
- /// Initializes a new instance of .
+ /// Initializes a new instance of .
///
/// The .
/// The .
- public VirtualPathRazorPageFactory(
+ public DefaultRazorPageFactoryProvider(
IRazorCompilationService razorCompilationService,
ICompilerCacheProvider compilerCacheProvider)
{
@@ -46,7 +46,7 @@ namespace Microsoft.AspNet.Mvc.Razor
}
///
- public IRazorPage CreateInstance(string relativePath)
+ public RazorPageFactoryResult CreateFactory(string relativePath)
{
if (relativePath == null)
{
@@ -58,18 +58,33 @@ namespace Microsoft.AspNet.Mvc.Razor
// For tilde slash paths, drop the leading ~ to make it work with the underlying IFileProvider.
relativePath = relativePath.Substring(1);
}
-
var result = CompilerCache.GetOrAdd(relativePath, _compileDelegate);
-
- if (result == CompilerCacheResult.FileNotFound)
+ if (result.Success)
{
- return null;
+ var pageFactory = GetPageFactory(result.CompilationResult.CompiledType, relativePath);
+ return new RazorPageFactoryResult(pageFactory, result.ExpirationTokens);
}
+ else
+ {
+ return new RazorPageFactoryResult(result.ExpirationTokens);
+ }
+ }
- var page = (IRazorPage)Activator.CreateInstance(result.CompilationResult.CompiledType);
- page.Path = relativePath;
-
- return page;
+ ///
+ /// Creates a factory for .
+ ///
+ /// The to produce an instance of
+ /// from.
+ /// The application relative path of the page.
+ /// A factory for .
+ protected virtual Func GetPageFactory(Type compiledType, string relativePath)
+ {
+ return () =>
+ {
+ var page = (IRazorPage)Activator.CreateInstance(compiledType);
+ page.Path = relativePath;
+ return page;
+ };
}
}
}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Mvc.Razor/DefaultViewLocationCache.cs b/src/Microsoft.AspNet.Mvc.Razor/DefaultViewLocationCache.cs
deleted file mode 100644
index 1530e299c4..0000000000
--- a/src/Microsoft.AspNet.Mvc.Razor/DefaultViewLocationCache.cs
+++ /dev/null
@@ -1,176 +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 System.Collections.Concurrent;
-using System.Collections.Generic;
-using Microsoft.Extensions.Internal;
-
-namespace Microsoft.AspNet.Mvc.Razor
-{
- ///
- /// Default implementation of .
- ///
- public class DefaultViewLocationCache : IViewLocationCache
- {
- // A mapping of keys generated from ViewLocationExpanderContext to view locations.
- private readonly ConcurrentDictionary _cache;
-
- ///
- /// Initializes a new instance of .
- ///
- public DefaultViewLocationCache()
- {
- _cache = new ConcurrentDictionary(
- ViewLocationCacheKeyComparer.Instance);
- }
-
- ///
- public ViewLocationCacheResult Get(ViewLocationExpanderContext context)
- {
- if (context == null)
- {
- throw new ArgumentNullException(nameof(context));
- }
-
- var cacheKey = GenerateKey(context, copyViewExpanderValues: false);
- ViewLocationCacheResult result;
- if (_cache.TryGetValue(cacheKey, out result))
- {
- return result;
- }
-
- return ViewLocationCacheResult.None;
- }
-
- ///
- public void Set(
- ViewLocationExpanderContext context,
- ViewLocationCacheResult value)
- {
- if (context == null)
- {
- throw new ArgumentNullException(nameof(context));
- }
-
- var cacheKey = GenerateKey(context, copyViewExpanderValues: true);
- _cache.TryAdd(cacheKey, value);
- }
-
- // Internal for unit testing
- internal static ViewLocationCacheKey GenerateKey(
- ViewLocationExpanderContext context,
- bool copyViewExpanderValues)
- {
- var controller = RazorViewEngine.GetNormalizedRouteValue(
- context.ActionContext,
- RazorViewEngine.ControllerKey);
-
- var area = RazorViewEngine.GetNormalizedRouteValue(
- context.ActionContext,
- RazorViewEngine.AreaKey);
-
-
- var values = context.Values;
- if (values != null && copyViewExpanderValues)
- {
- // When performing a Get, avoid creating a copy of the values dictionary
- values = new Dictionary(values, StringComparer.Ordinal);
- }
-
- return new ViewLocationCacheKey(
- context.ViewName,
- controller,
- area,
- context.IsPartial,
- values);
- }
-
- // Internal for unit testing
- internal class ViewLocationCacheKeyComparer : IEqualityComparer
- {
- public static readonly ViewLocationCacheKeyComparer Instance = new ViewLocationCacheKeyComparer();
-
- public bool Equals(ViewLocationCacheKey x, ViewLocationCacheKey y)
- {
- if (x.IsPartial != y.IsPartial ||
- !string.Equals(x.ViewName, y.ViewName, StringComparison.Ordinal) ||
- !string.Equals(x.ControllerName, y.ControllerName, StringComparison.Ordinal) ||
- !string.Equals(x.AreaName, y.AreaName, StringComparison.Ordinal))
- {
- return false;
- }
-
- if (ReferenceEquals(x.Values, y.Values))
- {
- return true;
- }
-
- if (x.Values == null || y.Values == null || (x.Values.Count != y.Values.Count))
- {
- return false;
- }
-
- foreach (var item in x.Values)
- {
- string yValue;
- if (!y.Values.TryGetValue(item.Key, out yValue) ||
- !string.Equals(item.Value, yValue, StringComparison.Ordinal))
- {
- return false;
- }
- }
-
- return true;
- }
-
- public int GetHashCode(ViewLocationCacheKey key)
- {
- var hashCodeCombiner = HashCodeCombiner.Start();
- hashCodeCombiner.Add(key.IsPartial ? 1 : 0);
- hashCodeCombiner.Add(key.ViewName, StringComparer.Ordinal);
- hashCodeCombiner.Add(key.ControllerName, StringComparer.Ordinal);
- hashCodeCombiner.Add(key.AreaName, StringComparer.Ordinal);
-
- if (key.Values != null)
- {
- foreach (var item in key.Values)
- {
- hashCodeCombiner.Add(item.Key, StringComparer.Ordinal);
- hashCodeCombiner.Add(item.Value, StringComparer.Ordinal);
- }
- }
-
- return hashCodeCombiner;
- }
- }
-
- // Internal for unit testing
- internal struct ViewLocationCacheKey
- {
- public ViewLocationCacheKey(
- string viewName,
- string controllerName,
- string areaName,
- bool isPartial,
- IDictionary values)
- {
- ViewName = viewName;
- ControllerName = controllerName;
- AreaName = areaName;
- IsPartial = isPartial;
- Values = values;
- }
-
- public string ViewName { get; }
-
- public string ControllerName { get; }
-
- public string AreaName { get; }
-
- public bool IsPartial { get; }
-
- public IDictionary Values { get; }
- }
- }
-}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Mvc.Razor/DependencyInjection/MvcRazorMvcCoreBuilderExtensions.cs b/src/Microsoft.AspNet.Mvc.Razor/DependencyInjection/MvcRazorMvcCoreBuilderExtensions.cs
index 23625126bd..15d26bfba0 100644
--- a/src/Microsoft.AspNet.Mvc.Razor/DependencyInjection/MvcRazorMvcCoreBuilderExtensions.cs
+++ b/src/Microsoft.AspNet.Mvc.Razor/DependencyInjection/MvcRazorMvcCoreBuilderExtensions.cs
@@ -131,8 +131,6 @@ namespace Microsoft.Extensions.DependencyInjection
services.TryAddSingleton();
- // Caches view locations that are valid for the lifetime of the application.
- services.TryAddSingleton();
services.TryAdd(ServiceDescriptor.Singleton(serviceProvider =>
{
var cachedFileProvider = serviceProvider.GetRequiredService>();
@@ -147,10 +145,8 @@ namespace Microsoft.Extensions.DependencyInjection
// In the default scenario the following services are singleton by virtue of being initialized as part of
// creating the singleton RazorViewEngine instance.
- services.TryAddTransient();
- services.TryAddTransient();
+ services.TryAddTransient();
services.TryAddTransient();
- services.TryAddTransient();
services.TryAddTransient();
// This caches Razor page activation details that are valid for the lifetime of the application.
diff --git a/src/Microsoft.AspNet.Mvc.Razor/IRazorPageFactory.cs b/src/Microsoft.AspNet.Mvc.Razor/IRazorPageFactoryProvider.cs
similarity index 63%
rename from src/Microsoft.AspNet.Mvc.Razor/IRazorPageFactory.cs
rename to src/Microsoft.AspNet.Mvc.Razor/IRazorPageFactoryProvider.cs
index b4cc1a011e..7f183c853d 100644
--- a/src/Microsoft.AspNet.Mvc.Razor/IRazorPageFactory.cs
+++ b/src/Microsoft.AspNet.Mvc.Razor/IRazorPageFactoryProvider.cs
@@ -6,13 +6,13 @@ namespace Microsoft.AspNet.Mvc.Razor
///
/// Defines methods that are used for creating instances at a given path.
///
- public interface IRazorPageFactory
+ public interface IRazorPageFactoryProvider
{
///
- /// Creates a for the specified path.
+ /// Creates a factory for the specified path.
///
/// The path to locate the page.
- /// The IRazorPage instance if it exists, null otherwise.
- IRazorPage CreateInstance(string relativePath);
+ /// The instance.
+ RazorPageFactoryResult CreateFactory(string relativePath);
}
}
diff --git a/src/Microsoft.AspNet.Mvc.Razor/IRazorViewFactory.cs b/src/Microsoft.AspNet.Mvc.Razor/IRazorViewFactory.cs
deleted file mode 100644
index c49731f8ae..0000000000
--- a/src/Microsoft.AspNet.Mvc.Razor/IRazorViewFactory.cs
+++ /dev/null
@@ -1,23 +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 Microsoft.AspNet.Mvc.ViewEngines;
-
-namespace Microsoft.AspNet.Mvc.Razor
-{
- ///
- /// Defines methods to create instances with a given .
- ///
- public interface IRazorViewFactory
- {
- ///
- /// Creates a providing it with the to execute.
- ///
- /// The that was used to locate Layout pages
- /// that will be part of 's execution.
- /// The instance to execute.
- /// Determines if the view is to be executed as a partial.
- /// A instance that renders the contents of the
- IView GetView(IRazorViewEngine viewEngine, IRazorPage page, bool isPartial);
- }
-}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Mvc.Razor/IViewLocationCache.cs b/src/Microsoft.AspNet.Mvc.Razor/IViewLocationCache.cs
deleted file mode 100644
index e3378a8890..0000000000
--- a/src/Microsoft.AspNet.Mvc.Razor/IViewLocationCache.cs
+++ /dev/null
@@ -1,27 +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.
-
-namespace Microsoft.AspNet.Mvc.Razor
-{
- ///
- /// Specifies the contracts for caching view locations generated by .
- ///
- public interface IViewLocationCache
- {
- ///
- /// Gets a cached view location based on the specified .
- ///
- /// The for the current view location
- /// expansion.
- /// The cached location, if available, null otherwise.
- ViewLocationCacheResult Get(ViewLocationExpanderContext context);
-
- ///
- /// Adds a cache entry for values specified by .
- ///
- /// The for the current view location
- /// expansion.
- /// The view location that is to be cached.
- void Set(ViewLocationExpanderContext context, ViewLocationCacheResult value);
- }
-}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Mvc.Razor/IViewStartProvider.cs b/src/Microsoft.AspNet.Mvc.Razor/IViewStartProvider.cs
deleted file mode 100644
index f5f711dce7..0000000000
--- a/src/Microsoft.AspNet.Mvc.Razor/IViewStartProvider.cs
+++ /dev/null
@@ -1,21 +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.Collections.Generic;
-
-namespace Microsoft.AspNet.Mvc.Razor
-{
- ///
- /// Defines methods for locating ViewStart pages that are applicable to a page.
- ///
- public interface IViewStartProvider
- {
- ///
- /// Given a view path, returns a sequence of ViewStart instances
- /// that are applicable to the specified view.
- ///
- /// The path of the page to locate ViewStart files for.
- /// A sequence of that represent ViewStart.
- IEnumerable GetViewStartPages(string path);
- }
-}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Mvc.Razor/Precompilation/RazorPreCompiler.cs b/src/Microsoft.AspNet.Mvc.Razor/Precompilation/RazorPreCompiler.cs
index db06d8a689..64d78d3443 100644
--- a/src/Microsoft.AspNet.Mvc.Razor/Precompilation/RazorPreCompiler.cs
+++ b/src/Microsoft.AspNet.Mvc.Razor/Precompilation/RazorPreCompiler.cs
@@ -146,8 +146,9 @@ namespace Microsoft.AspNet.Mvc.Razor.Precompilation
return null;
}
- return GeneratePrecompiledAssembly(syntaxTrees.Where(tree => tree != null),
- razorFiles.Where(file => file != null));
+ return GeneratePrecompiledAssembly(
+ syntaxTrees.Where(tree => tree != null),
+ razorFiles.Where(file => file != null));
}
protected virtual RazorFileInfoCollection GeneratePrecompiledAssembly(
@@ -174,13 +175,15 @@ namespace Microsoft.AspNet.Mvc.Razor.Precompilation
var references = CompileContext.Compilation.References
.Concat(new[] { applicationReference });
- var preCompilationOptions = CompilationSettings.CompilationOptions
- .WithOutputKind(OutputKind.DynamicallyLinkedLibrary);
+ var preCompilationOptions = CompilationSettings
+ .CompilationOptions
+ .WithOutputKind(OutputKind.DynamicallyLinkedLibrary);
- var compilation = CSharpCompilation.Create(assemblyResourceName,
- options: preCompilationOptions,
- syntaxTrees: syntaxTrees,
- references: references);
+ var compilation = CSharpCompilation.Create(
+ assemblyResourceName,
+ options: preCompilationOptions,
+ syntaxTrees: syntaxTrees,
+ references: references);
var generateSymbols = GenerateSymbols && SymbolsUtility.SupportsSymbolsGeneration();
// These streams are returned to the runtime and consequently cannot be disposed.
@@ -279,9 +282,10 @@ namespace Microsoft.AspNet.Mvc.Razor.Precompilation
if (results.Success)
{
- var syntaxTree = SyntaxTreeGenerator.Generate(results.GeneratedCode,
- fileInfo.FileInfo.PhysicalPath,
- CompilationSettings);
+ var syntaxTree = SyntaxTreeGenerator.Generate(
+ results.GeneratedCode,
+ fileInfo.FileInfo.PhysicalPath,
+ CompilationSettings);
var fullTypeName = results.GetMainClassName(host, syntaxTree);
if (fullTypeName != null)
@@ -297,9 +301,10 @@ namespace Microsoft.AspNet.Mvc.Razor.Precompilation
}
else
{
- var diagnostics = results.ParserErrors
- .Select(error => error.ToDiagnostics(fileInfo.FileInfo.PhysicalPath))
- .ToList();
+ var diagnostics = results
+ .ParserErrors
+ .Select(error => error.ToDiagnostics(fileInfo.FileInfo.PhysicalPath))
+ .ToList();
return new PrecompilationCacheEntry(diagnostics);
}
diff --git a/src/Microsoft.AspNet.Mvc.Razor/RazorPageFactoryResult.cs b/src/Microsoft.AspNet.Mvc.Razor/RazorPageFactoryResult.cs
new file mode 100644
index 0000000000..eed74e32b1
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.Razor/RazorPageFactoryResult.cs
@@ -0,0 +1,72 @@
+// 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 System.Collections.Generic;
+using Microsoft.Extensions.Primitives;
+
+namespace Microsoft.AspNet.Mvc.Razor
+{
+ ///
+ /// Result of .
+ ///
+ public struct RazorPageFactoryResult
+ {
+ ///
+ /// Initializes a new instance of with the
+ /// specified .
+ ///
+ /// One or more instances.
+ public RazorPageFactoryResult(IList expirationTokens)
+ {
+ if (expirationTokens == null)
+ {
+ throw new ArgumentNullException(nameof(expirationTokens));
+ }
+
+ ExpirationTokens = expirationTokens;
+ RazorPageFactory = null;
+ }
+
+ ///
+ /// Initializes a new instance of with the
+ /// specified factory.
+ ///
+ /// The factory.
+ /// One or more instances.
+ public RazorPageFactoryResult(
+ Func razorPageFactory,
+ IList expirationTokens)
+ {
+ if (razorPageFactory == null)
+ {
+ throw new ArgumentNullException(nameof(razorPageFactory));
+ }
+
+ if (expirationTokens == null)
+ {
+ throw new ArgumentNullException(nameof(expirationTokens));
+ }
+
+ RazorPageFactory = razorPageFactory;
+ ExpirationTokens = expirationTokens;
+ }
+
+ ///
+ /// The factory.
+ ///
+ /// This property is null when is false.
+ public Func RazorPageFactory { get; }
+
+ ///
+ /// One or more s associated with this instance of
+ /// .
+ ///
+ public IList ExpirationTokens { get; }
+
+ ///
+ /// Gets a value that determines if the page was successfully located.
+ ///
+ public bool Success => RazorPageFactory != null;
+ }
+}
diff --git a/src/Microsoft.AspNet.Mvc.Razor/RazorPageResult.cs b/src/Microsoft.AspNet.Mvc.Razor/RazorPageResult.cs
index 92628d18a5..89ee7a66e6 100644
--- a/src/Microsoft.AspNet.Mvc.Razor/RazorPageResult.cs
+++ b/src/Microsoft.AspNet.Mvc.Razor/RazorPageResult.cs
@@ -7,9 +7,9 @@ using System.Collections.Generic;
namespace Microsoft.AspNet.Mvc.Razor
{
///
- /// Represents the results of locating a .
+ /// Result of locating a .
///
- public class RazorPageResult
+ public struct RazorPageResult
{
///
/// Initializes a new instance of for a successful discovery.
@@ -30,6 +30,7 @@ namespace Microsoft.AspNet.Mvc.Razor
Name = name;
Page = page;
+ SearchedLocations = null;
}
///
@@ -50,6 +51,7 @@ namespace Microsoft.AspNet.Mvc.Razor
}
Name = name;
+ Page = null;
SearchedLocations = searchedLocations;
}
diff --git a/src/Microsoft.AspNet.Mvc.Razor/RazorView.cs b/src/Microsoft.AspNet.Mvc.Razor/RazorView.cs
index 5c5620203f..05b58c63ca 100644
--- a/src/Microsoft.AspNet.Mvc.Razor/RazorView.cs
+++ b/src/Microsoft.AspNet.Mvc.Razor/RazorView.cs
@@ -22,7 +22,6 @@ namespace Microsoft.AspNet.Mvc.Razor
{
private readonly IRazorViewEngine _viewEngine;
private readonly IRazorPageActivator _pageActivator;
- private readonly IViewStartProvider _viewStartProvider;
private readonly HtmlEncoder _htmlEncoder;
private IPageExecutionListenerFeature _pageExecutionFeature;
@@ -31,22 +30,22 @@ namespace Microsoft.AspNet.Mvc.Razor
///
/// The used to locate Layout pages.
/// The used to activate pages.
- /// The used for discovery of _ViewStart
+ /// The sequence of instances executed as _ViewStarts.
+ ///
/// The instance to execute.
/// The HTML encoder.
/// Determines if the view is to be executed as a partial.
- /// pages
public RazorView(
IRazorViewEngine viewEngine,
IRazorPageActivator pageActivator,
- IViewStartProvider viewStartProvider,
+ IReadOnlyList viewStartPages,
IRazorPage razorPage,
HtmlEncoder htmlEncoder,
bool isPartial)
{
_viewEngine = viewEngine;
_pageActivator = pageActivator;
- _viewStartProvider = viewStartProvider;
+ ViewStartPages = viewStartPages;
RazorPage = razorPage;
_htmlEncoder = htmlEncoder;
IsPartial = isPartial;
@@ -68,6 +67,12 @@ namespace Microsoft.AspNet.Mvc.Razor
///
public bool IsPartial { get; }
+ ///
+ /// Gets the sequence of _ViewStart instances
+ /// that are executed by this view if is false.
+ ///
+ public IReadOnlyList ViewStartPages { get; }
+
private bool EnableInstrumentation
{
get { return _pageExecutionFeature != null; }
@@ -153,14 +158,13 @@ namespace Microsoft.AspNet.Mvc.Razor
private async Task RenderViewStartAsync(ViewContext context)
{
- var viewStarts = _viewStartProvider.GetViewStartPages(RazorPage.Path);
-
string layout = null;
var oldFilePath = context.ExecutingFilePath;
try
{
- foreach (var viewStart in viewStarts)
+ for (var i = 0; i < ViewStartPages.Count; i++)
{
+ var viewStart = ViewStartPages[i];
context.ExecutingFilePath = viewStart.Path;
// Copy the layout value from the previous view start (if any) to the current.
viewStart.Layout = layout;
diff --git a/src/Microsoft.AspNet.Mvc.Razor/RazorViewEngine.cs b/src/Microsoft.AspNet.Mvc.Razor/RazorViewEngine.cs
index 50bf30c650..e2cf7fc24d 100644
--- a/src/Microsoft.AspNet.Mvc.Razor/RazorViewEngine.cs
+++ b/src/Microsoft.AspNet.Mvc.Razor/RazorViewEngine.cs
@@ -5,10 +5,12 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
-using System.Linq;
+using System.Text.Encodings.Web;
using Microsoft.AspNet.Mvc.Routing;
using Microsoft.AspNet.Mvc.ViewEngines;
+using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.OptionsModel;
+using Microsoft.Extensions.Primitives;
namespace Microsoft.AspNet.Mvc.Razor
{
@@ -22,41 +24,34 @@ namespace Microsoft.AspNet.Mvc.Razor
public class RazorViewEngine : IRazorViewEngine
{
private const string ViewExtension = ".cshtml";
- internal const string ControllerKey = "controller";
- internal const string AreaKey = "area";
+ private const string ControllerKey = "controller";
+ private const string AreaKey = "area";
+ private static readonly ViewLocationCacheItem[] EmptyViewStartLocationCacheItems =
+ new ViewLocationCacheItem[0];
+ private static readonly TimeSpan _cacheExpirationDuration = TimeSpan.FromMinutes(20);
- private static readonly IEnumerable _viewLocationFormats = new[]
- {
- "/Views/{1}/{0}" + ViewExtension,
- "/Views/Shared/{0}" + ViewExtension,
- };
-
- private static readonly IEnumerable _areaViewLocationFormats = new[]
- {
- "/Areas/{2}/Views/{1}/{0}" + ViewExtension,
- "/Areas/{2}/Views/Shared/{0}" + ViewExtension,
- "/Views/Shared/{0}" + ViewExtension,
- };
-
- private readonly IRazorPageFactory _pageFactory;
- private readonly IRazorViewFactory _viewFactory;
+ private readonly IRazorPageFactoryProvider _pageFactory;
private readonly IList _viewLocationExpanders;
- private readonly IViewLocationCache _viewLocationCache;
+ private readonly IRazorPageActivator _pageActivator;
+ private readonly HtmlEncoder _htmlEncoder;
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the .
///
- /// The page factory used for creating instances.
public RazorViewEngine(
- IRazorPageFactory pageFactory,
- IRazorViewFactory viewFactory,
- IOptions optionsAccessor,
- IViewLocationCache viewLocationCache)
+ IRazorPageFactoryProvider pageFactory,
+ IRazorPageActivator pageActivator,
+ HtmlEncoder htmlEncoder,
+ IOptions optionsAccessor)
{
_pageFactory = pageFactory;
- _viewFactory = viewFactory;
+ _pageActivator = pageActivator;
_viewLocationExpanders = optionsAccessor.Value.ViewLocationExpanders;
- _viewLocationCache = viewLocationCache;
+ _htmlEncoder = htmlEncoder;
+ ViewLookupCache = new MemoryCache(new MemoryCacheOptions
+ {
+ CompactOnMemoryPressure = false
+ });
}
///
@@ -72,10 +67,11 @@ namespace Microsoft.AspNet.Mvc.Razor
/// For example, the view for the Test action of HomeController should be located at
/// /Views/Home/Test.cshtml. Locations such as /views/home/test.cshtml would not be discovered
///
- public virtual IEnumerable ViewLocationFormats
+ public virtual IEnumerable ViewLocationFormats { get; } = new[]
{
- get { return _viewLocationFormats; }
- }
+ "/Views/{1}/{0}" + ViewExtension,
+ "/Views/Shared/{0}" + ViewExtension,
+ };
///
/// Gets the locations where this instance of will search for views within an
@@ -92,15 +88,20 @@ namespace Microsoft.AspNet.Mvc.Razor
/// For example, the view for the Test action of HomeController should be located at
/// /Views/Home/Test.cshtml. Locations such as /views/home/test.cshtml would not be discovered
///
- public virtual IEnumerable AreaViewLocationFormats
+ public virtual IEnumerable AreaViewLocationFormats { get; } = new[]
{
- get { return _areaViewLocationFormats; }
- }
+ "/Areas/{2}/Views/{1}/{0}" + ViewExtension,
+ "/Areas/{2}/Views/Shared/{0}" + ViewExtension,
+ "/Views/Shared/{0}" + ViewExtension,
+ };
+
+ ///
+ /// A cache for results of view lookups.
+ ///
+ protected IMemoryCache ViewLookupCache { get; }
///
- public ViewEngineResult FindView(
- ActionContext context,
- string viewName)
+ public ViewEngineResult FindView(ActionContext context, string viewName)
{
if (context == null)
{
@@ -112,14 +113,12 @@ namespace Microsoft.AspNet.Mvc.Razor
throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(viewName));
}
- var pageResult = GetRazorPageResult(context, viewName, isPartial: false);
- return CreateViewEngineResult(pageResult, _viewFactory, isPartial: false);
+ var pageResult = GetViewLocationCacheResult(context, viewName, isPartial: false);
+ return CreateViewEngineResult(pageResult, viewName, isPartial: false);
}
///
- public ViewEngineResult FindPartialView(
- ActionContext context,
- string partialViewName)
+ public ViewEngineResult FindPartialView(ActionContext context, string partialViewName)
{
if (context == null)
{
@@ -131,14 +130,12 @@ namespace Microsoft.AspNet.Mvc.Razor
throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(partialViewName));
}
- var pageResult = GetRazorPageResult(context, partialViewName, isPartial: true);
- return CreateViewEngineResult(pageResult, _viewFactory, isPartial: true);
+ var pageResult = GetViewLocationCacheResult(context, partialViewName, isPartial: true);
+ return CreateViewEngineResult(pageResult, partialViewName, isPartial: true);
}
///
- public RazorPageResult FindPage(
- ActionContext context,
- string pageName)
+ public RazorPageResult FindPage(ActionContext context, string pageName)
{
if (context == null)
{
@@ -150,7 +147,16 @@ namespace Microsoft.AspNet.Mvc.Razor
throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(pageName));
}
- return GetRazorPageResult(context, pageName, isPartial: true);
+ var cacheResult = GetViewLocationCacheResult(context, pageName, isPartial: true);
+ if (cacheResult.Success)
+ {
+ var razorPage = cacheResult.ViewEntry.PageFactory();
+ return new RazorPageResult(pageName, razorPage);
+ }
+ else
+ {
+ return new RazorPageResult(pageName, cacheResult.SearchedLocations);
+ }
}
///
@@ -166,9 +172,7 @@ namespace Microsoft.AspNet.Mvc.Razor
/// for traditional routes to get route values
/// produces consistently cased results.
///
- public static string GetNormalizedRouteValue(
- ActionContext context,
- string key)
+ public static string GetNormalizedRouteValue(ActionContext context, string key)
{
if (context == null)
{
@@ -228,26 +232,14 @@ namespace Microsoft.AspNet.Mvc.Razor
return stringRouteValue;
}
- private RazorPageResult GetRazorPageResult(
+ private ViewLocationCacheResult GetViewLocationCacheResult(
ActionContext context,
string pageName,
bool isPartial)
{
if (IsApplicationRelativePath(pageName))
{
- var applicationRelativePath = pageName;
- if (!pageName.EndsWith(ViewExtension, StringComparison.OrdinalIgnoreCase))
- {
- applicationRelativePath += ViewExtension;
- }
-
- var page = _pageFactory.CreateInstance(applicationRelativePath);
- if (page != null)
- {
- return new RazorPageResult(pageName, page);
- }
-
- return new RazorPageResult(pageName, new[] { pageName });
+ return LocatePageFromPath(pageName, isPartial);
}
else
{
@@ -255,105 +247,221 @@ namespace Microsoft.AspNet.Mvc.Razor
}
}
- private RazorPageResult LocatePageFromViewLocations(
- ActionContext context,
+ private ViewLocationCacheResult LocatePageFromPath(string pageName, bool isPartial)
+ {
+ var applicationRelativePath = pageName;
+ if (!pageName.EndsWith(ViewExtension, StringComparison.OrdinalIgnoreCase))
+ {
+ applicationRelativePath += ViewExtension;
+ }
+
+ var cacheKey = new ViewLocationCacheKey(applicationRelativePath, isPartial);
+ ViewLocationCacheResult cacheResult;
+ if (!ViewLookupCache.TryGetValue(cacheKey, out cacheResult))
+ {
+ var expirationTokens = new HashSet();
+ cacheResult = CreateCacheResult(cacheKey, expirationTokens, applicationRelativePath, isPartial);
+
+ var cacheEntryOptions = new MemoryCacheEntryOptions();
+ cacheEntryOptions.SetSlidingExpiration(_cacheExpirationDuration);
+ foreach (var expirationToken in expirationTokens)
+ {
+ cacheEntryOptions.AddExpirationToken(expirationToken);
+ }
+
+ // No views were found at the specified location. Create a not found result.
+ if (cacheResult == null)
+ {
+ cacheResult = new ViewLocationCacheResult(new[] { pageName });
+ }
+
+ cacheResult = ViewLookupCache.Set(
+ cacheKey,
+ cacheResult,
+ cacheEntryOptions);
+ }
+
+ return cacheResult;
+ }
+
+ private ViewLocationCacheResult LocatePageFromViewLocations(
+ ActionContext actionContext,
string pageName,
bool isPartial)
{
- // Initialize the dictionary for the typical case of having controller and action tokens.
- var areaName = GetNormalizedRouteValue(context, AreaKey);
+ var controllerName = GetNormalizedRouteValue(actionContext, ControllerKey);
+ var areaName = GetNormalizedRouteValue(actionContext, AreaKey);
+ var expanderContext = new ViewLocationExpanderContext(
+ actionContext,
+ pageName,
+ controllerName,
+ areaName,
+ isPartial);
+ Dictionary expanderValues = null;
- // Only use the area view location formats if we have an area token.
- var viewLocations = !string.IsNullOrEmpty(areaName) ? AreaViewLocationFormats :
- ViewLocationFormats;
-
- var expanderContext = new ViewLocationExpanderContext(context, pageName, isPartial);
if (_viewLocationExpanders.Count > 0)
{
- expanderContext.Values = new Dictionary(StringComparer.Ordinal);
+ expanderValues = new Dictionary(StringComparer.Ordinal);
+ expanderContext.Values = expanderValues;
- // 1. Populate values from viewLocationExpanders.
// Perf: Avoid allocations
- for( var i = 0; i < _viewLocationExpanders.Count; i++)
+ for (var i = 0; i < _viewLocationExpanders.Count; i++)
{
_viewLocationExpanders[i].PopulateValues(expanderContext);
}
}
- // 2. With the values that we've accumumlated so far, check if we have a cached result.
- IEnumerable locationsToSearch = null;
- var cachedResult = _viewLocationCache.Get(expanderContext);
- if (!cachedResult.Equals(ViewLocationCacheResult.None))
- {
- if (cachedResult.IsFoundResult)
- {
- var page = _pageFactory.CreateInstance(cachedResult.ViewLocation);
+ var cacheKey = new ViewLocationCacheKey(
+ expanderContext.ViewName,
+ expanderContext.ControllerName,
+ expanderContext.ViewName,
+ expanderContext.IsPartial,
+ expanderValues);
- if (page != null)
- {
- // 2a We have a cache entry where a view was previously found.
- return new RazorPageResult(pageName, page);
- }
- }
- else
- {
- locationsToSearch = cachedResult.SearchedLocations;
- }
+ ViewLocationCacheResult cacheResult;
+ if (!ViewLookupCache.TryGetValue(cacheKey, out cacheResult))
+ {
+ cacheResult = OnCacheMiss(expanderContext, cacheKey);
}
- if (locationsToSearch == null)
+ return cacheResult;
+ }
+
+ private ViewLocationCacheResult OnCacheMiss(
+ ViewLocationExpanderContext expanderContext,
+ ViewLocationCacheKey cacheKey)
+ {
+ // Only use the area view location formats if we have an area token.
+ var viewLocations = !string.IsNullOrEmpty(expanderContext.AreaName) ?
+ AreaViewLocationFormats :
+ ViewLocationFormats;
+
+ for (var i = 0; i < _viewLocationExpanders.Count; i++)
{
- // 2b. We did not find a cached location or did not find a IRazorPage at the cached location.
- // The cached value has expired and we need to look up the page.
- foreach (var expander in _viewLocationExpanders)
- {
- viewLocations = expander.ExpandViewLocations(expanderContext, viewLocations);
- }
-
- var controllerName = GetNormalizedRouteValue(context, ControllerKey);
-
- locationsToSearch = viewLocations.Select(
- location => string.Format(
- CultureInfo.InvariantCulture,
- location,
- pageName,
- controllerName,
- areaName
- ));
+ viewLocations = _viewLocationExpanders[i].ExpandViewLocations(expanderContext, viewLocations);
}
- // 3. Use the expanded locations to look up a page.
+ ViewLocationCacheResult cacheResult = null;
var searchedLocations = new List();
- foreach (var path in locationsToSearch)
+ var expirationTokens = new HashSet();
+ foreach (var location in viewLocations)
{
- var page = _pageFactory.CreateInstance(path);
- if (page != null)
+ var path = string.Format(
+ CultureInfo.InvariantCulture,
+ location,
+ expanderContext.ViewName,
+ expanderContext.ControllerName,
+ expanderContext.AreaName);
+
+ cacheResult = CreateCacheResult(cacheKey, expirationTokens, path, expanderContext.IsPartial);
+ if (cacheResult != null)
{
- // 3a. We found a page. Cache the set of values that produced it and return a found result.
- _viewLocationCache.Set(expanderContext, new ViewLocationCacheResult(path, searchedLocations));
- return new RazorPageResult(pageName, page);
+ break;
}
searchedLocations.Add(path);
}
- // 3b. We did not find a page for any of the paths.
- _viewLocationCache.Set(expanderContext, new ViewLocationCacheResult(searchedLocations));
- return new RazorPageResult(pageName, searchedLocations);
+ // No views were found at the specified location. Create a not found result.
+ if (cacheResult == null)
+ {
+ cacheResult = new ViewLocationCacheResult(searchedLocations);
+ }
+
+ var cacheEntryOptions = new MemoryCacheEntryOptions();
+ cacheEntryOptions.SetSlidingExpiration(_cacheExpirationDuration);
+ foreach (var expirationToken in expirationTokens)
+ {
+ cacheEntryOptions.AddExpirationToken(expirationToken);
+ }
+
+ return ViewLookupCache.Set(cacheKey, cacheResult, cacheEntryOptions);
+ }
+
+ private ViewLocationCacheResult CreateCacheResult(
+ ViewLocationCacheKey cacheKey,
+ HashSet expirationTokens,
+ string relativePath,
+ bool isPartial)
+ {
+ var factoryResult = _pageFactory.CreateFactory(relativePath);
+ if (factoryResult.ExpirationTokens != null)
+ {
+ for (var i = 0; i < factoryResult.ExpirationTokens.Count; i++)
+ {
+ expirationTokens.Add(factoryResult.ExpirationTokens[i]);
+ }
+ }
+
+ if (factoryResult.Success)
+ {
+ // Don't need to lookup _ViewStarts for partials.
+ var viewStartPages = isPartial ?
+ EmptyViewStartLocationCacheItems :
+ GetViewStartPages(relativePath, expirationTokens);
+
+ return new ViewLocationCacheResult(
+ new ViewLocationCacheItem(factoryResult.RazorPageFactory, relativePath),
+ viewStartPages);
+ }
+
+ return null;
+ }
+
+ private IReadOnlyList GetViewStartPages(
+ string path,
+ HashSet expirationTokens)
+ {
+ var viewStartPages = new List();
+ foreach (var viewStartPath in ViewHierarchyUtility.GetViewStartLocations(path))
+ {
+ var result = _pageFactory.CreateFactory(viewStartPath);
+ if (result.ExpirationTokens != null)
+ {
+ for (var i = 0; i < result.ExpirationTokens.Count; i++)
+ {
+ expirationTokens.Add(result.ExpirationTokens[i]);
+ }
+ }
+
+ if (result.Success)
+ {
+ // Populate the viewStartPages list so that _ViewStarts appear in the order the need to be
+ // executed (closest last, furthest first). This is the reverse order in which
+ // ViewHierarchyUtility.GetViewStartLocations returns _ViewStarts.
+ viewStartPages.Insert(0, new ViewLocationCacheItem(result.RazorPageFactory, viewStartPath));
+ }
+ }
+
+ return viewStartPages;
}
private ViewEngineResult CreateViewEngineResult(
- RazorPageResult result,
- IRazorViewFactory razorViewFactory,
+ ViewLocationCacheResult result,
+ string viewName,
bool isPartial)
{
- if (result.SearchedLocations != null)
+ if (!result.Success)
{
- return ViewEngineResult.NotFound(result.Name, result.SearchedLocations);
+ return ViewEngineResult.NotFound(viewName, result.SearchedLocations);
}
- var view = razorViewFactory.GetView(this, result.Page, isPartial);
- return ViewEngineResult.Found(result.Name, view);
+ var page = result.ViewEntry.PageFactory();
+ var viewStarts = new IRazorPage[result.ViewStartEntries.Count];
+ for (var i = 0; i < viewStarts.Length; i++)
+ {
+ var viewStartItem = result.ViewStartEntries[i];
+ viewStarts[i] = result.ViewStartEntries[i].PageFactory();
+ }
+
+ var view = new RazorView(
+ this,
+ _pageActivator,
+ viewStarts,
+ page,
+ _htmlEncoder,
+ isPartial);
+ return ViewEngineResult.Found(viewName, view);
}
private static bool IsApplicationRelativePath(string name)
diff --git a/src/Microsoft.AspNet.Mvc.Razor/RazorViewFactory.cs b/src/Microsoft.AspNet.Mvc.Razor/RazorViewFactory.cs
deleted file mode 100644
index 661926a481..0000000000
--- a/src/Microsoft.AspNet.Mvc.Razor/RazorViewFactory.cs
+++ /dev/null
@@ -1,62 +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 System.Text.Encodings.Web;
-using Microsoft.AspNet.Mvc.ViewEngines;
-
-namespace Microsoft.AspNet.Mvc.Razor
-{
- ///
- /// Represents the default implementation that creates
- /// instances with a given .
- ///
- public class RazorViewFactory : IRazorViewFactory
- {
- private readonly HtmlEncoder _htmlEncoder;
- private readonly IRazorPageActivator _pageActivator;
- private readonly IViewStartProvider _viewStartProvider;
-
- ///
- /// Initializes a new instance of RazorViewFactory
- ///
- /// The used to activate pages.
- /// The used for discovery of _ViewStart
- /// pages
- public RazorViewFactory(
- IRazorPageActivator pageActivator,
- IViewStartProvider viewStartProvider,
- HtmlEncoder htmlEncoder)
- {
- _pageActivator = pageActivator;
- _viewStartProvider = viewStartProvider;
- _htmlEncoder = htmlEncoder;
- }
-
- ///
- public IView GetView(
- IRazorViewEngine viewEngine,
- IRazorPage page,
- bool isPartial)
- {
- if (viewEngine == null)
- {
- throw new ArgumentNullException(nameof(viewEngine));
- }
-
- if (page == null)
- {
- throw new ArgumentNullException(nameof(page));
- }
-
- var razorView = new RazorView(
- viewEngine,
- _pageActivator,
- _viewStartProvider,
- page,
- _htmlEncoder,
- isPartial);
- return razorView;
- }
- }
-}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Mvc.Razor/ViewLocationCacheItem.cs b/src/Microsoft.AspNet.Mvc.Razor/ViewLocationCacheItem.cs
new file mode 100644
index 0000000000..a87488c423
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.Razor/ViewLocationCacheItem.cs
@@ -0,0 +1,34 @@
+// 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;
+
+namespace Microsoft.AspNet.Mvc.Razor
+{
+ ///
+ /// An item in .
+ ///
+ public struct ViewLocationCacheItem
+ {
+ ///
+ /// Initializes a new instance of .
+ ///
+ /// The factory.
+ /// The application relative path of the .
+ public ViewLocationCacheItem(Func razorPageFactory, string location)
+ {
+ PageFactory = razorPageFactory;
+ Location = location;
+ }
+
+ ///
+ /// Gets the application relative path of the
+ ///
+ public string Location { get; }
+
+ ///
+ /// Gets the factory.
+ ///
+ public Func PageFactory { get; }
+ }
+}
diff --git a/src/Microsoft.AspNet.Mvc.Razor/ViewLocationCacheKey.cs b/src/Microsoft.AspNet.Mvc.Razor/ViewLocationCacheKey.cs
new file mode 100644
index 0000000000..f73900402d
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.Razor/ViewLocationCacheKey.cs
@@ -0,0 +1,147 @@
+// 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 System.Collections.Generic;
+using Microsoft.Extensions.Internal;
+
+namespace Microsoft.AspNet.Mvc.Razor
+{
+ ///
+ /// Key for entries in .
+ ///
+ public struct ViewLocationCacheKey : IEquatable
+ {
+ ///
+ /// Initializes a new instance of .
+ ///
+ /// The view name or path.
+ /// Determines if the view is a partial.
+ public ViewLocationCacheKey(
+ string viewName,
+ bool isPartial)
+ : this(
+ viewName,
+ controllerName: null,
+ areaName: null,
+ isPartial: isPartial,
+ values: null)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of .
+ ///
+ /// The view name.
+ /// The controller name.
+ /// The area name.
+ /// Determines if the view is a partial.
+ /// Values from instances.
+ public ViewLocationCacheKey(
+ string viewName,
+ string controllerName,
+ string areaName,
+ bool isPartial,
+ IReadOnlyDictionary values)
+ {
+ ViewName = viewName;
+ ControllerName = controllerName;
+ AreaName = areaName;
+ IsPartial = isPartial;
+ ViewLocationExpanderValues = values;
+ }
+
+ ///
+ /// Gets the view name.
+ ///
+ public string ViewName { get; }
+
+ ///
+ /// Gets the controller name.
+ ///
+ public string ControllerName { get; }
+
+ ///
+ /// Gets the area name.
+ ///
+ public string AreaName { get; }
+
+ ///
+ /// Determines if the view is a partial.
+ ///
+ public bool IsPartial { get; }
+
+ ///
+ /// Gets the values populated by instances.
+ ///
+ public IReadOnlyDictionary ViewLocationExpanderValues { get; }
+
+ ///
+ public bool Equals(ViewLocationCacheKey y)
+ {
+ if (IsPartial != y.IsPartial ||
+ !string.Equals(ViewName, y.ViewName, StringComparison.Ordinal) ||
+ !string.Equals(ControllerName, y.ControllerName, StringComparison.Ordinal) ||
+ !string.Equals(AreaName, y.AreaName, StringComparison.Ordinal))
+ {
+ return false;
+ }
+
+ if (ReferenceEquals(ViewLocationExpanderValues, y.ViewLocationExpanderValues))
+ {
+ return true;
+ }
+
+ if (ViewLocationExpanderValues == null ||
+ y.ViewLocationExpanderValues == null ||
+ (ViewLocationExpanderValues.Count != y.ViewLocationExpanderValues.Count))
+ {
+ return false;
+ }
+
+ foreach (var item in ViewLocationExpanderValues)
+ {
+ string yValue;
+ if (!y.ViewLocationExpanderValues.TryGetValue(item.Key, out yValue) ||
+ !string.Equals(item.Value, yValue, StringComparison.Ordinal))
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ ///
+ public override bool Equals(object obj)
+ {
+ if (obj is ViewLocationCacheKey)
+ {
+ return Equals((ViewLocationCacheKey)obj);
+ }
+
+ return false;
+ }
+
+ ///
+ public override int GetHashCode()
+ {
+ var hashCodeCombiner = HashCodeCombiner.Start();
+ hashCodeCombiner.Add(IsPartial ? 1 : 0);
+ hashCodeCombiner.Add(ViewName, StringComparer.Ordinal);
+ hashCodeCombiner.Add(ControllerName, StringComparer.Ordinal);
+ hashCodeCombiner.Add(AreaName, StringComparer.Ordinal);
+
+ if (ViewLocationExpanderValues != null)
+ {
+ foreach (var item in ViewLocationExpanderValues)
+ {
+ hashCodeCombiner.Add(item.Key, StringComparer.Ordinal);
+ hashCodeCombiner.Add(item.Value, StringComparer.Ordinal);
+ }
+ }
+
+ return hashCodeCombiner;
+ }
+ }
+}
diff --git a/src/Microsoft.AspNet.Mvc.Razor/ViewLocationCacheResult.cs b/src/Microsoft.AspNet.Mvc.Razor/ViewLocationCacheResult.cs
index 3f27f16636..80ff746c5e 100644
--- a/src/Microsoft.AspNet.Mvc.Razor/ViewLocationCacheResult.cs
+++ b/src/Microsoft.AspNet.Mvc.Razor/ViewLocationCacheResult.cs
@@ -3,36 +3,32 @@
using System;
using System.Collections.Generic;
-using System.Linq;
-using Microsoft.Extensions.Internal;
namespace Microsoft.AspNet.Mvc.Razor
{
///
- /// Result of lookups.
+ /// Result of view location cache lookup.
///
- public struct ViewLocationCacheResult : IEquatable
+ public class ViewLocationCacheResult
{
///
/// Initializes a new instance of
/// for a view that was successfully found at the specified location.
///
- /// The view location.
- /// Locations that were searched
- /// in addition to .
+ /// The for the found view.
+ /// s for applicable _ViewStarts.
public ViewLocationCacheResult(
- string foundLocation,
- IEnumerable searchedLocations)
- : this(searchedLocations)
+ ViewLocationCacheItem view,
+ IReadOnlyList viewStarts)
{
- if (foundLocation == null)
+ if (viewStarts == null)
{
- throw new ArgumentNullException(nameof(foundLocation));
+ throw new ArgumentNullException(nameof(viewStarts));
}
- ViewLocation = foundLocation;
- SearchedLocations = searchedLocations;
- IsFoundResult = true;
+ ViewEntry = view;
+ ViewStartEntries = viewStarts;
+ Success = true;
}
///
@@ -48,27 +44,26 @@ namespace Microsoft.AspNet.Mvc.Razor
}
SearchedLocations = searchedLocations;
- ViewLocation = null;
- IsFoundResult = false;
}
///
- /// A that represents a cache miss.
+ /// for the located view.
///
- public static readonly ViewLocationCacheResult None = new ViewLocationCacheResult(Enumerable.Empty());
+ /// null if is false.
+ public ViewLocationCacheItem ViewEntry { get; }
///
- /// The location the view was found.
+ /// s for applicable _ViewStarts.
///
- /// This is available if is true.
- public string ViewLocation { get; }
+ /// null if is false.
+ public IReadOnlyList ViewStartEntries { get; }
///
/// The sequence of locations that were searched.
///
///
- /// When is true this includes all paths that were search prior to finding
- /// a view at . When is false, this includes
+ /// When is true this includes all paths that were search prior to finding
+ /// a view at . When is false, this includes
/// all search paths.
///
public IEnumerable SearchedLocations { get; }
@@ -76,55 +71,6 @@ namespace Microsoft.AspNet.Mvc.Razor
///
/// Gets a value that indicates whether the view was successfully found.
///
- public bool IsFoundResult { get; }
-
- ///
- public bool Equals(ViewLocationCacheResult other)
- {
- if (IsFoundResult != other.IsFoundResult)
- {
- return false;
- }
-
- if (IsFoundResult)
- {
- return string.Equals(ViewLocation, other.ViewLocation, StringComparison.Ordinal);
- }
- else
- {
- if (SearchedLocations == other.SearchedLocations)
- {
- return true;
- }
-
- if (SearchedLocations == null || other.SearchedLocations == null)
- {
- return false;
- }
-
- return Enumerable.SequenceEqual(SearchedLocations, other.SearchedLocations, StringComparer.Ordinal);
- }
- }
-
- ///
- public override int GetHashCode()
- {
- var hashCodeCombiner = HashCodeCombiner.Start();
- hashCodeCombiner.Add(IsFoundResult);
-
- if (IsFoundResult)
- {
- hashCodeCombiner.Add(ViewLocation, StringComparer.Ordinal);
- }
- else if (SearchedLocations != null)
- {
- foreach (var location in SearchedLocations)
- {
- hashCodeCombiner.Add(location, StringComparer.Ordinal);
- }
- }
-
- return hashCodeCombiner;
- }
+ public bool Success { get; }
}
}
diff --git a/src/Microsoft.AspNet.Mvc.Razor/ViewLocationExpanderContext.cs b/src/Microsoft.AspNet.Mvc.Razor/ViewLocationExpanderContext.cs
index 35c4f6bc04..f120ccb7ee 100644
--- a/src/Microsoft.AspNet.Mvc.Razor/ViewLocationExpanderContext.cs
+++ b/src/Microsoft.AspNet.Mvc.Razor/ViewLocationExpanderContext.cs
@@ -16,10 +16,14 @@ namespace Microsoft.AspNet.Mvc.Razor
///
/// The for the current executing action.
/// The view name.
+ /// The controller name.
+ /// The area name.
/// Determines if the view being discovered is a partial.
public ViewLocationExpanderContext(
ActionContext actionContext,
string viewName,
+ string controllerName,
+ string areaName,
bool isPartial)
{
if (actionContext == null)
@@ -34,6 +38,8 @@ namespace Microsoft.AspNet.Mvc.Razor
ActionContext = actionContext;
ViewName = viewName;
+ ControllerName = controllerName;
+ AreaName = areaName;
IsPartial = isPartial;
}
@@ -47,6 +53,16 @@ namespace Microsoft.AspNet.Mvc.Razor
///
public string ViewName { get; }
+ ///
+ /// Gets the controller name.
+ ///
+ public string ControllerName { get; }
+
+ ///
+ /// Gets the area name.
+ ///
+ public string AreaName { get; }
+
///
/// Gets a value that determines if a partial view is being discovered.
///
diff --git a/src/Microsoft.AspNet.Mvc.Razor/ViewStartProvider.cs b/src/Microsoft.AspNet.Mvc.Razor/ViewStartProvider.cs
deleted file mode 100644
index c0bdcde162..0000000000
--- a/src/Microsoft.AspNet.Mvc.Razor/ViewStartProvider.cs
+++ /dev/null
@@ -1,41 +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 System.Collections.Generic;
-using System.Linq;
-
-namespace Microsoft.AspNet.Mvc.Razor
-{
- ///
- public class ViewStartProvider : IViewStartProvider
- {
- private readonly IRazorPageFactory _pageFactory;
-
- public ViewStartProvider(IRazorPageFactory pageFactory)
- {
- _pageFactory = pageFactory;
- }
-
- ///
- public IEnumerable GetViewStartPages(string path)
- {
- if (path == null)
- {
- throw new ArgumentNullException(nameof(path));
- }
-
- var viewStartLocations = ViewHierarchyUtility.GetViewStartLocations(path);
- var viewStarts = viewStartLocations.Select(_pageFactory.CreateInstance)
- .Where(p => p != null)
- .ToArray();
-
- // GetViewStartLocations return ViewStarts inside-out that is the _ViewStart closest to the page
- // is the first: e.g. [ /Views/Home/_ViewStart, /Views/_ViewStart, /_ViewStart ]
- // However they need to be executed outside in, so we'll reverse the sequence.
- Array.Reverse(viewStarts);
-
- return viewStarts;
- }
- }
-}
\ No newline at end of file
diff --git a/test/Microsoft.AspNet.Mvc.Razor.Test/Compilation/CompilationResultTest.cs b/test/Microsoft.AspNet.Mvc.Razor.Test/Compilation/CompilationResultTest.cs
index c87305defd..d2f78df1b5 100644
--- a/test/Microsoft.AspNet.Mvc.Razor.Test/Compilation/CompilationResultTest.cs
+++ b/test/Microsoft.AspNet.Mvc.Razor.Test/Compilation/CompilationResultTest.cs
@@ -15,7 +15,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Compilation
// Arrange
var compilationFailure = new CompilationFailure("test", Enumerable.Empty());
var failures = new[] { compilationFailure };
- var result = CompilationResult.Failed(failures);
+ var result = new CompilationResult(failures);
// Act and Assert
Assert.Null(result.CompiledType);
diff --git a/test/Microsoft.AspNet.Mvc.Razor.Test/Compilation/CompilerCacheTest.cs b/test/Microsoft.AspNet.Mvc.Razor.Test/Compilation/CompilerCacheTest.cs
index 1b11ba0010..3203c8920c 100644
--- a/test/Microsoft.AspNet.Mvc.Razor.Test/Compilation/CompilerCacheTest.cs
+++ b/test/Microsoft.AspNet.Mvc.Razor.Test/Compilation/CompilerCacheTest.cs
@@ -36,8 +36,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Compilation
var result = cache.GetOrAdd("/some/path", ThrowsIfCalled);
// Assert
- Assert.Same(CompilerCacheResult.FileNotFound, result);
- Assert.Null(result.CompilationResult);
+ Assert.False(result.Success);
}
[Fact]
@@ -48,18 +47,14 @@ namespace Microsoft.AspNet.Mvc.Razor.Compilation
fileProvider.AddFile(ViewPath, "some content");
var cache = new CompilerCache(fileProvider);
var type = typeof(TestView);
- var expected = UncachedCompilationResult.Successful(type, "hello world");
+ var expected = new CompilationResult(type);
// Act
var result = cache.GetOrAdd(ViewPath, _ => expected);
// Assert
- Assert.NotSame(CompilerCacheResult.FileNotFound, result);
- var actual = Assert.IsType(result.CompilationResult);
- Assert.NotNull(actual);
- Assert.Same(expected, actual);
- Assert.Equal("hello world", actual.CompiledContent);
- Assert.Same(type, actual.CompiledType);
+ Assert.True(result.Success);
+ Assert.Same(type, result.CompilationResult.CompiledType);
}
[Theory]
@@ -75,15 +70,13 @@ namespace Microsoft.AspNet.Mvc.Razor.Compilation
fileProvider.AddFile(viewPath, "some content");
var cache = new CompilerCache(fileProvider);
var type = typeof(TestView);
- var expected = UncachedCompilationResult.Successful(type, "hello world");
+ var expected = new CompilationResult(type);
// Act - 1
var result1 = cache.GetOrAdd(@"Areas\Finances\Views\Home\Index.cshtml", _ => expected);
// Assert - 1
- var compilationResult = Assert.IsType(result1.CompilationResult);
- Assert.Same(expected, compilationResult);
- Assert.Same(type, compilationResult.CompiledType);
+ Assert.Same(type, result1.CompilationResult.CompiledType);
// Act - 2
var result2 = cache.GetOrAdd(relativePath, ThrowsIfCalled);
@@ -93,21 +86,21 @@ namespace Microsoft.AspNet.Mvc.Razor.Compilation
}
[Fact]
- public void GetOrAdd_ReturnsFileNotFoundIfFileWasDeleted()
+ public void GetOrAdd_ReturnsFailedCompilationResult_IfFileWasRemovedFromFileSystem()
{
// Arrange
var fileProvider = new TestFileProvider();
fileProvider.AddFile(ViewPath, "some content");
var cache = new CompilerCache(fileProvider);
var type = typeof(TestView);
- var expected = UncachedCompilationResult.Successful(type, "hello world");
+ var expected = new CompilationResult(type);
// Act 1
var result1 = cache.GetOrAdd(ViewPath, _ => expected);
// Assert 1
- Assert.NotSame(CompilerCacheResult.FileNotFound, result1);
- Assert.Same(expected, result1.CompilationResult);
+ Assert.True(result1.Success);
+ Assert.Same(expected.CompiledType, result1.CompilationResult.CompiledType);
// Act 2
// Delete the file from the file system and set it's expiration token.
@@ -116,8 +109,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Compilation
var result2 = cache.GetOrAdd(ViewPath, ThrowsIfCalled);
// Assert 2
- Assert.Same(CompilerCacheResult.FileNotFound, result2);
- Assert.Null(result2.CompilationResult);
+ Assert.False(result2.Success);
}
[Fact]
@@ -127,22 +119,22 @@ namespace Microsoft.AspNet.Mvc.Razor.Compilation
var fileProvider = new TestFileProvider();
fileProvider.AddFile(ViewPath, "some content");
var cache = new CompilerCache(fileProvider);
- var expected1 = UncachedCompilationResult.Successful(typeof(TestView), "hello world");
- var expected2 = UncachedCompilationResult.Successful(typeof(DifferentView), "different content");
+ var expected1 = new CompilationResult(typeof(TestView));
+ var expected2 = new CompilationResult(typeof(DifferentView));
// Act 1
var result1 = cache.GetOrAdd(ViewPath, _ => expected1);
// Assert 1
- Assert.NotSame(CompilerCacheResult.FileNotFound, result1);
- Assert.Same(expected1, result1.CompilationResult);
+ Assert.True(result1.Success);
+ Assert.Same(typeof(TestView), result1.CompilationResult.CompiledType);
// Act 2
// Verify we're getting cached results.
var result2 = cache.GetOrAdd(ViewPath, ThrowsIfCalled);
// Assert 2
- Assert.NotSame(CompilerCacheResult.FileNotFound, result2);
+ Assert.True(result2.Success);
Assert.Same(expected1.CompiledType, result2.CompilationResult.CompiledType);
// Act 3
@@ -150,8 +142,8 @@ namespace Microsoft.AspNet.Mvc.Razor.Compilation
var result3 = cache.GetOrAdd(ViewPath, _ => expected2);
// Assert 3
- Assert.NotSame(CompilerCacheResult.FileNotFound, result3);
- Assert.Same(expected2, result3.CompilationResult);
+ Assert.True(result3.Success);
+ Assert.Same(expected2.CompiledType, result3.CompilationResult.CompiledType);
}
[Theory]
@@ -162,22 +154,22 @@ namespace Microsoft.AspNet.Mvc.Razor.Compilation
var fileProvider = new TestFileProvider();
fileProvider.AddFile(ViewPath, "some content");
var cache = new CompilerCache(fileProvider);
- var expected1 = UncachedCompilationResult.Successful(typeof(TestView), "hello world");
- var expected2 = UncachedCompilationResult.Successful(typeof(DifferentView), "different content");
+ var expected1 = new CompilationResult(typeof(TestView));
+ var expected2 = new CompilationResult(typeof(DifferentView));
// Act 1
var result1 = cache.GetOrAdd(ViewPath, _ => expected1);
// Assert 1
- Assert.NotSame(CompilerCacheResult.FileNotFound, result1);
- Assert.Same(expected1, result1.CompilationResult);
+ Assert.True(result1.Success);
+ Assert.Same(expected1.CompiledType, result1.CompilationResult.CompiledType);
// Act 2
// Verify we're getting cached results.
var result2 = cache.GetOrAdd(ViewPath, ThrowsIfCalled);
// Assert 2
- Assert.NotSame(CompilerCacheResult.FileNotFound, result2);
+ Assert.True(result2.Success);
Assert.Same(expected1.CompiledType, result2.CompilationResult.CompiledType);
// Act 3
@@ -185,8 +177,8 @@ namespace Microsoft.AspNet.Mvc.Razor.Compilation
var result3 = cache.GetOrAdd(ViewPath, _ => expected2);
// Assert 2
- Assert.NotSame(CompilerCacheResult.FileNotFound, result3);
- Assert.Same(expected2, result3.CompilationResult);
+ Assert.True(result3.Success);
+ Assert.Same(expected2.CompiledType, result3.CompilationResult.CompiledType);
}
[Fact]
@@ -198,21 +190,20 @@ namespace Microsoft.AspNet.Mvc.Razor.Compilation
fileProvider.AddFile(ViewPath, "some content");
var cache = new CompilerCache(fileProvider);
var type = typeof(TestView);
- var expected = UncachedCompilationResult.Successful(type, "hello world");
+ var expected = new CompilationResult(type);
// Act 1
var result1 = cache.GetOrAdd(ViewPath, _ => expected);
// Assert 1
- Assert.NotSame(CompilerCacheResult.FileNotFound, result1);
- Assert.Same(expected, result1.CompilationResult);
+ Assert.True(result1.Success);
+ Assert.Same(type, result1.CompilationResult.CompiledType);
// Act 2
var result2 = cache.GetOrAdd(ViewPath, ThrowsIfCalled);
// Assert 2
- Assert.NotSame(CompilerCacheResult.FileNotFound, result2);
- Assert.IsType(result2.CompilationResult);
+ Assert.True(result2.Success);
Assert.Same(type, result2.CompilationResult.CompiledType);
mockFileProvider.Verify(v => v.GetFileInfo(ViewPath), Times.Once());
}
@@ -228,7 +219,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Compilation
var result = cache.GetOrAdd(PrecompiledViewsPath, ThrowsIfCalled);
// Assert
- Assert.NotSame(CompilerCacheResult.FileNotFound, result);
+ Assert.True(result.Success);
Assert.Same(typeof(PreCompile), result.CompilationResult.CompiledType);
}
@@ -245,7 +236,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Compilation
var result = cache.GetOrAdd(PrecompiledViewsPath, ThrowsIfCalled);
// Assert
- Assert.NotSame(CompilerCacheResult.FileNotFound, result);
+ Assert.True(result.Success);
Assert.Same(typeof(PreCompile), result.CompilationResult.CompiledType);
}
@@ -263,7 +254,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Compilation
var result = cache.GetOrAdd(PrecompiledViewsPath, ThrowsIfCalled);
// Assert
- Assert.NotSame(CompilerCacheResult.FileNotFound, result);
+ Assert.True(result.Success);
Assert.Same(typeof(PreCompile), result.CompilationResult.CompiledType);
}
@@ -274,26 +265,26 @@ namespace Microsoft.AspNet.Mvc.Razor.Compilation
var fileProvider = new TestFileProvider();
fileProvider.AddFile(ViewPath, "some content");
var cache = new CompilerCache(fileProvider, _precompiledViews);
- var expected = CompilationResult.Successful(typeof(TestView));
+ var expected = new CompilationResult(typeof(TestView));
// Act 1
var result1 = cache.GetOrAdd(ViewPath, _ => expected);
// Assert 1
- Assert.Same(expected, result1.CompilationResult);
+ Assert.Same(typeof(TestView), result1.CompilationResult.CompiledType);
// Act 2
var result2 = cache.GetOrAdd(ViewPath, ThrowsIfCalled);
// Assert 2
- Assert.NotSame(CompilerCacheResult.FileNotFound, result2);
+ Assert.True(result2.Success);
Assert.Same(typeof(TestView), result2.CompilationResult.CompiledType);
// Act 3
var result3 = cache.GetOrAdd(PrecompiledViewsPath, ThrowsIfCalled);
// Assert 3
- Assert.NotSame(CompilerCacheResult.FileNotFound, result2);
+ Assert.True(result2.Success);
Assert.Same(typeof(PreCompile), result3.CompilationResult.CompiledType);
}
diff --git a/test/Microsoft.AspNet.Mvc.Razor.Test/Compilation/RazorCompilationServiceTest.cs b/test/Microsoft.AspNet.Mvc.Razor.Test/Compilation/RazorCompilationServiceTest.cs
index e7717aebbd..5d0079f9a2 100644
--- a/test/Microsoft.AspNet.Mvc.Razor.Test/Compilation/RazorCompilationServiceTest.cs
+++ b/test/Microsoft.AspNet.Mvc.Razor.Test/Compilation/RazorCompilationServiceTest.cs
@@ -36,7 +36,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Compilation
var compiler = new Mock();
compiler.Setup(c => c.Compile(relativeFileInfo, It.IsAny()))
- .Returns(CompilationResult.Successful(typeof(RazorCompilationServiceTest)));
+ .Returns(new CompilationResult(typeof(RazorCompilationServiceTest)));
var razorService = new RazorCompilationService(compiler.Object, host.Object, GetOptions());
@@ -106,7 +106,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Compilation
.Returns(Stream.Null);
var relativeFileInfo = new RelativeFileInfo(fileInfo.Object, @"Views\index\home.cshtml");
- var compilationResult = CompilationResult.Successful(typeof(object));
+ var compilationResult = new CompilationResult(typeof(object));
var compiler = new Mock();
compiler.Setup(c => c.Compile(relativeFileInfo, code))
.Returns(compilationResult)
@@ -117,7 +117,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Compilation
var result = razorService.Compile(relativeFileInfo);
// Assert
- Assert.Same(compilationResult, result);
+ Assert.Same(compilationResult.CompiledType, result.CompiledType);
compiler.Verify();
}
diff --git a/test/Microsoft.AspNet.Mvc.Razor.Test/Compilation/RoslynCompilationServiceTest.cs b/test/Microsoft.AspNet.Mvc.Razor.Test/Compilation/RoslynCompilationServiceTest.cs
index 4666a09127..64c10a3e06 100644
--- a/test/Microsoft.AspNet.Mvc.Razor.Test/Compilation/RoslynCompilationServiceTest.cs
+++ b/test/Microsoft.AspNet.Mvc.Razor.Test/Compilation/RoslynCompilationServiceTest.cs
@@ -20,7 +20,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Compilation
public class RoslynCompilationServiceTest
{
[Fact]
- public void Compile_ReturnsUncachedCompilationResultWithCompiledContent()
+ public void Compile_ReturnsCompilationResult()
{
// Arrange
var content = @"
@@ -29,29 +29,31 @@ public class MyTestType {}";
var libraryExporter = GetLibraryExporter();
var compilerOptionsProvider = new Mock();
- compilerOptionsProvider.Setup(p => p.GetCompilerOptions(applicationEnvironment.ApplicationName,
- applicationEnvironment.RuntimeFramework,
- applicationEnvironment.Configuration))
- .Returns(new CompilerOptions());
+ compilerOptionsProvider
+ .Setup(p => p.GetCompilerOptions(
+ applicationEnvironment.ApplicationName,
+ applicationEnvironment.RuntimeFramework,
+ applicationEnvironment.Configuration))
+ .Returns(new CompilerOptions());
var mvcRazorHost = new Mock();
mvcRazorHost.SetupGet(m => m.MainClassNamePrefix)
.Returns(string.Empty);
- var compilationService = new RoslynCompilationService(applicationEnvironment,
- libraryExporter,
- compilerOptionsProvider.Object,
- mvcRazorHost.Object,
- GetOptions());
- var relativeFileInfo = new RelativeFileInfo(new TestFileInfo { PhysicalPath = "SomePath" },
+ var compilationService = new RoslynCompilationService(
+ applicationEnvironment,
+ libraryExporter,
+ compilerOptionsProvider.Object,
+ mvcRazorHost.Object,
+ GetOptions());
+ var relativeFileInfo = new RelativeFileInfo(
+ new TestFileInfo { PhysicalPath = "SomePath" },
"some-relative-path");
// Act
var result = compilationService.Compile(relativeFileInfo, content);
// Assert
- var uncachedResult = Assert.IsType(result);
Assert.Equal("MyTestType", result.CompiledType.Name);
- Assert.Equal(content, uncachedResult.CompiledContent);
}
[Fact]
@@ -67,19 +69,22 @@ this should fail";
var libraryExporter = GetLibraryExporter();
var compilerOptionsProvider = new Mock();
- compilerOptionsProvider.Setup(p => p.GetCompilerOptions(applicationEnvironment.ApplicationName,
- applicationEnvironment.RuntimeFramework,
- applicationEnvironment.Configuration))
- .Returns(new CompilerOptions());
+ compilerOptionsProvider
+ .Setup(p => p.GetCompilerOptions(
+ applicationEnvironment.ApplicationName,
+ applicationEnvironment.RuntimeFramework,
+ applicationEnvironment.Configuration))
+ .Returns(new CompilerOptions());
var mvcRazorHost = Mock.Of();
var fileProvider = new TestFileProvider();
var fileInfo = fileProvider.AddFile(viewPath, fileContent);
- var compilationService = new RoslynCompilationService(applicationEnvironment,
- libraryExporter,
- compilerOptionsProvider.Object,
- mvcRazorHost,
- GetOptions(fileProvider));
+ var compilationService = new RoslynCompilationService(
+ applicationEnvironment,
+ libraryExporter,
+ compilerOptionsProvider.Object,
+ mvcRazorHost,
+ GetOptions(fileProvider));
var relativeFileInfo = new RelativeFileInfo(fileInfo, "some-relative-path");
// Act
@@ -103,18 +108,22 @@ this should fail";
var libraryExporter = GetLibraryExporter();
var compilerOptionsProvider = new Mock();
- compilerOptionsProvider.Setup(p => p.GetCompilerOptions(applicationEnvironment.ApplicationName,
- applicationEnvironment.RuntimeFramework,
- applicationEnvironment.Configuration))
- .Returns(new CompilerOptions());
+ compilerOptionsProvider
+ .Setup(p => p.GetCompilerOptions(
+ applicationEnvironment.ApplicationName,
+ applicationEnvironment.RuntimeFramework,
+ applicationEnvironment.Configuration))
+ .Returns(new CompilerOptions());
var mvcRazorHost = Mock.Of();
- var compilationService = new RoslynCompilationService(applicationEnvironment,
- libraryExporter,
- compilerOptionsProvider.Object,
- mvcRazorHost,
- GetOptions());
- var relativeFileInfo = new RelativeFileInfo(new TestFileInfo { Content = fileContent },
+ var compilationService = new RoslynCompilationService(
+ applicationEnvironment,
+ libraryExporter,
+ compilerOptionsProvider.Object,
+ mvcRazorHost,
+ GetOptions());
+ var relativeFileInfo = new RelativeFileInfo(
+ new TestFileInfo { Content = fileContent },
"some-relative-path");
// Act
@@ -141,10 +150,12 @@ this should fail";
var libraryExporter = GetLibraryExporter();
var compilerOptionsProvider = new Mock();
- compilerOptionsProvider.Setup(p => p.GetCompilerOptions(applicationEnvironment.ApplicationName,
- applicationEnvironment.RuntimeFramework,
- applicationEnvironment.Configuration))
- .Returns(new CompilerOptions());
+ compilerOptionsProvider
+ .Setup(p => p.GetCompilerOptions(
+ applicationEnvironment.ApplicationName,
+ applicationEnvironment.RuntimeFramework,
+ applicationEnvironment.Configuration))
+ .Returns(new CompilerOptions());
var mvcRazorHost = Mock.Of();
var mockFileInfo = new Mock();
@@ -153,11 +164,12 @@ this should fail";
var fileProvider = new TestFileProvider();
fileProvider.AddFile(path, mockFileInfo.Object);
- var compilationService = new RoslynCompilationService(applicationEnvironment,
- libraryExporter,
- compilerOptionsProvider.Object,
- mvcRazorHost,
- GetOptions(fileProvider));
+ var compilationService = new RoslynCompilationService(
+ applicationEnvironment,
+ libraryExporter,
+ compilerOptionsProvider.Object,
+ mvcRazorHost,
+ GetOptions(fileProvider));
var relativeFileInfo = new RelativeFileInfo(mockFileInfo.Object, path);
@@ -187,20 +199,24 @@ public class MyNonCustomDefinedClass {}
var libraryExporter = GetLibraryExporter();
var compilerOptionsProvider = new Mock();
- compilerOptionsProvider.Setup(p => p.GetCompilerOptions(applicationEnvironment.ApplicationName,
- applicationEnvironment.RuntimeFramework,
- applicationEnvironment.Configuration))
- .Returns(new CompilerOptions { Defines = new[] { "MY_CUSTOM_DEFINE" } });
+ compilerOptionsProvider
+ .Setup(p => p.GetCompilerOptions(
+ applicationEnvironment.ApplicationName,
+ applicationEnvironment.RuntimeFramework,
+ applicationEnvironment.Configuration))
+ .Returns(new CompilerOptions { Defines = new[] { "MY_CUSTOM_DEFINE" } });
var mvcRazorHost = new Mock();
mvcRazorHost.SetupGet(m => m.MainClassNamePrefix)
.Returns("My");
- var compilationService = new RoslynCompilationService(applicationEnvironment,
- libraryExporter,
- compilerOptionsProvider.Object,
- mvcRazorHost.Object,
- GetOptions());
- var relativeFileInfo = new RelativeFileInfo(new TestFileInfo { PhysicalPath = "SomePath" },
+ var compilationService = new RoslynCompilationService(
+ applicationEnvironment,
+ libraryExporter,
+ compilerOptionsProvider.Object,
+ mvcRazorHost.Object,
+ GetOptions());
+ var relativeFileInfo = new RelativeFileInfo(
+ new TestFileInfo { PhysicalPath = "SomePath" },
"some-relative-path");
// Act
@@ -222,21 +238,25 @@ public class NotRazorPrefixType {}";
var libraryExporter = GetLibraryExporter();
var compilerOptionsProvider = new Mock();
- compilerOptionsProvider.Setup(p => p.GetCompilerOptions(applicationEnvironment.ApplicationName,
- applicationEnvironment.RuntimeFramework,
- applicationEnvironment.Configuration))
- .Returns(new CompilerOptions());
+ compilerOptionsProvider
+ .Setup(p => p.GetCompilerOptions(
+ applicationEnvironment.ApplicationName,
+ applicationEnvironment.RuntimeFramework,
+ applicationEnvironment.Configuration))
+ .Returns(new CompilerOptions());
var mvcRazorHost = new Mock();
mvcRazorHost.SetupGet(m => m.MainClassNamePrefix)
.Returns("RazorPrefix");
- var compilationService = new RoslynCompilationService(applicationEnvironment,
- libraryExporter,
- compilerOptionsProvider.Object,
- mvcRazorHost.Object,
- GetOptions());
+ var compilationService = new RoslynCompilationService(
+ applicationEnvironment,
+ libraryExporter,
+ compilerOptionsProvider.Object,
+ mvcRazorHost.Object,
+ GetOptions());
- var relativeFileInfo = new RelativeFileInfo(new TestFileInfo { PhysicalPath = "SomePath" },
+ var relativeFileInfo = new RelativeFileInfo(
+ new TestFileInfo { PhysicalPath = "SomePath" },
"some-relative-path");
// Act
@@ -356,27 +376,33 @@ public class NotRazorPrefixType {}";
private static ILibraryExporter GetLibraryExporter()
{
var fileReference = new Mock();
- fileReference.SetupGet(f => f.Path)
- .Returns(typeof(string).Assembly.Location);
+ fileReference
+ .SetupGet(f => f.Path)
+ .Returns(typeof(string).Assembly.Location);
var libraryExport = new LibraryExport(fileReference.Object);
var libraryExporter = new Mock();
- libraryExporter.Setup(l => l.GetAllExports(It.IsAny()))
- .Returns(libraryExport);
+ libraryExporter
+ .Setup(l => l.GetAllExports(It.IsAny()))
+ .Returns(libraryExport);
return libraryExporter.Object;
}
private IApplicationEnvironment GetApplicationEnvironment()
{
var applicationEnvironment = new Mock();
- applicationEnvironment.SetupGet(a => a.ApplicationName)
- .Returns("MyApp");
- applicationEnvironment.SetupGet(a => a.RuntimeFramework)
- .Returns(new FrameworkName("ASPNET", new Version(5, 0)));
- applicationEnvironment.SetupGet(a => a.Configuration)
- .Returns("Debug");
- applicationEnvironment.SetupGet(a => a.ApplicationBasePath)
- .Returns("MyBasePath");
+ applicationEnvironment
+ .SetupGet(a => a.ApplicationName)
+ .Returns("MyApp");
+ applicationEnvironment
+ .SetupGet(a => a.RuntimeFramework)
+ .Returns(new FrameworkName("ASPNET", new Version(5, 0)));
+ applicationEnvironment
+ .SetupGet(a => a.Configuration)
+ .Returns("Debug");
+ applicationEnvironment
+ .SetupGet(a => a.ApplicationBasePath)
+ .Returns("MyBasePath");
return applicationEnvironment.Object;
}
@@ -388,7 +414,8 @@ public class NotRazorPrefixType {}";
FileProvider = fileProvider ?? new TestFileProvider()
};
var options = new Mock>();
- options.SetupGet(o => o.Value)
+ options
+ .SetupGet(o => o.Value)
.Returns(razorViewEngineOptions);
return options.Object;
diff --git a/test/Microsoft.AspNet.Mvc.Razor.Test/DefaultRazorPageFactoryProviderTest.cs b/test/Microsoft.AspNet.Mvc.Razor.Test/DefaultRazorPageFactoryProviderTest.cs
new file mode 100644
index 0000000000..dcd6d59cc5
--- /dev/null
+++ b/test/Microsoft.AspNet.Mvc.Razor.Test/DefaultRazorPageFactoryProviderTest.cs
@@ -0,0 +1,106 @@
+// 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 System.Threading.Tasks;
+using Microsoft.AspNet.Mvc.Razor.Compilation;
+using Microsoft.Extensions.Primitives;
+using Moq;
+using Xunit;
+
+namespace Microsoft.AspNet.Mvc.Razor.Test
+{
+ public class DefaultRazorPageFactoryProviderTest
+ {
+ [Fact]
+ public void CreateFactory_ReturnsExpirationTokensFromCompilerCache_ForUnsuccessfulResults()
+ {
+ // Arrange
+ var expirationTokens = new[]
+ {
+ Mock.Of(),
+ Mock.Of(),
+ };
+ var compilerCache = new Mock();
+ compilerCache
+ .Setup(f => f.GetOrAdd(It.IsAny(), It.IsAny>()))
+ .Returns(new CompilerCacheResult(expirationTokens));
+ var compilerCacheProvider = new Mock();
+ compilerCacheProvider
+ .SetupGet(c => c.Cache)
+ .Returns(compilerCache.Object);
+ var factoryProvider = new DefaultRazorPageFactoryProvider(
+ Mock.Of(),
+ compilerCacheProvider.Object);
+
+ // Act
+ var result = factoryProvider.CreateFactory("/file-does-not-exist");
+
+ // Assert
+ Assert.False(result.Success);
+ Assert.Equal(expirationTokens, result.ExpirationTokens);
+ }
+
+ [Fact]
+ public void CreateFactory_ReturnsExpirationTokensFromCompilerCache_ForSuccessfulResults()
+ {
+ // Arrange
+ var expirationTokens = new[]
+ {
+ Mock.Of(),
+ Mock.Of(),
+ };
+ var compilerCache = new Mock();
+ compilerCache
+ .Setup(f => f.GetOrAdd(It.IsAny(), It.IsAny>()))
+ .Returns(new CompilerCacheResult(new CompilationResult(typeof(object)), expirationTokens));
+ var compilerCacheProvider = new Mock();
+ compilerCacheProvider
+ .SetupGet(c => c.Cache)
+ .Returns(compilerCache.Object);
+ var factoryProvider = new DefaultRazorPageFactoryProvider(
+ Mock.Of(),
+ compilerCacheProvider.Object);
+
+ // Act
+ var result = factoryProvider.CreateFactory("/file-exists");
+
+ // Assert
+ Assert.True(result.Success);
+ Assert.Equal(expirationTokens, result.ExpirationTokens);
+ }
+
+ [Fact]
+ public void CreateFactory_ProducesDelegateThatSetsPagePath()
+ {
+ // Arrange
+ var compilerCache = new Mock();
+ compilerCache
+ .Setup(f => f.GetOrAdd(It.IsAny(), It.IsAny>()))
+ .Returns(new CompilerCacheResult(new CompilationResult(typeof(TestRazorPage)), new IChangeToken[0]));
+ var compilerCacheProvider = new Mock();
+ compilerCacheProvider
+ .SetupGet(c => c.Cache)
+ .Returns(compilerCache.Object);
+ var factoryProvider = new DefaultRazorPageFactoryProvider(
+ Mock.Of(),
+ compilerCacheProvider.Object);
+
+ // Act
+ var result = factoryProvider.CreateFactory("/file-exists");
+
+ // Assert
+ Assert.True(result.Success);
+ var actual = result.RazorPageFactory();
+ Assert.Equal("/file-exists", actual.Path);
+ }
+
+ private class TestRazorPage : RazorPage
+ {
+ public override Task ExecuteAsync()
+ {
+ throw new NotImplementedException();
+ }
+ }
+ }
+}
diff --git a/test/Microsoft.AspNet.Mvc.Razor.Test/DefaultViewLocationCacheTest.cs b/test/Microsoft.AspNet.Mvc.Razor.Test/DefaultViewLocationCacheTest.cs
deleted file mode 100644
index 1561f262bc..0000000000
--- a/test/Microsoft.AspNet.Mvc.Razor.Test/DefaultViewLocationCacheTest.cs
+++ /dev/null
@@ -1,522 +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 System.Collections.Generic;
-using Microsoft.AspNet.Http.Internal;
-using Microsoft.AspNet.Mvc.Abstractions;
-using Microsoft.AspNet.Mvc.Routing;
-using Microsoft.AspNet.Routing;
-using Xunit;
-
-namespace Microsoft.AspNet.Mvc.Razor
-{
- public class DefaultViewLocationCacheTest
- {
- public static IEnumerable