Refactor ICompilerCache to be instantiated via ICompilerCacheProvider

Fixes #2933
This commit is contained in:
Pranav K 2015-08-19 12:15:18 -07:00
parent e61ebca116
commit 05226a4a55
25 changed files with 371 additions and 407 deletions

17
Mvc.sln
View File

@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
VisualStudioVersion = 14.0.23017.0
VisualStudioVersion = 14.0.23107.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{DAAE4C74-D06F-4874-A166-33305D2643CE}"
EndProject
@ -122,8 +122,6 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "ControllersFromServicesWebS
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "ControllersFromServicesClassLibrary", "test\WebSites\ControllersFromServicesClassLibrary\ControllersFromServicesClassLibrary.xproj", "{551DC89E-2A13-4CF2-83D7-1ADD802443D5}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "RazorCompilerCacheWebSite", "test\WebSites\RazorCompilerCacheWebSite\RazorCompilerCacheWebSite.xproj", "{42C5D417-4060-48F4-BB28-E9E179007779}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.PageExecutionInstrumentation.Interfaces", "src\Microsoft.AspNet.PageExecutionInstrumentation.Interfaces\Microsoft.AspNet.PageExecutionInstrumentation.Interfaces.xproj", "{4DA2D7C1-A7B6-4C01-B57D-89E6EA4609DE}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "UserClassLibrary", "test\WebSites\UserClassLibrary\UserClassLibrary.xproj", "{C651F432-4EBE-41A6-BAD2-3E07CCBA209C}"
@ -758,18 +756,6 @@ Global
{551DC89E-2A13-4CF2-83D7-1ADD802443D5}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{551DC89E-2A13-4CF2-83D7-1ADD802443D5}.Release|x86.ActiveCfg = Release|Any CPU
{551DC89E-2A13-4CF2-83D7-1ADD802443D5}.Release|x86.Build.0 = Release|Any CPU
{42C5D417-4060-48F4-BB28-E9E179007779}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{42C5D417-4060-48F4-BB28-E9E179007779}.Debug|Any CPU.Build.0 = Debug|Any CPU
{42C5D417-4060-48F4-BB28-E9E179007779}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{42C5D417-4060-48F4-BB28-E9E179007779}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{42C5D417-4060-48F4-BB28-E9E179007779}.Debug|x86.ActiveCfg = Debug|Any CPU
{42C5D417-4060-48F4-BB28-E9E179007779}.Debug|x86.Build.0 = Debug|Any CPU
{42C5D417-4060-48F4-BB28-E9E179007779}.Release|Any CPU.ActiveCfg = Release|Any CPU
{42C5D417-4060-48F4-BB28-E9E179007779}.Release|Any CPU.Build.0 = Release|Any CPU
{42C5D417-4060-48F4-BB28-E9E179007779}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{42C5D417-4060-48F4-BB28-E9E179007779}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{42C5D417-4060-48F4-BB28-E9E179007779}.Release|x86.ActiveCfg = Release|Any CPU
{42C5D417-4060-48F4-BB28-E9E179007779}.Release|x86.Build.0 = Release|Any CPU
{4DA2D7C1-A7B6-4C01-B57D-89E6EA4609DE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4DA2D7C1-A7B6-4C01-B57D-89E6EA4609DE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4DA2D7C1-A7B6-4C01-B57D-89E6EA4609DE}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
@ -1213,7 +1199,6 @@ Global
{AC9BE567-540E-4C70-90C2-AAF021307A80} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
{983741B2-4424-4ED1-9B03-7675A67230C8} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
{551DC89E-2A13-4CF2-83D7-1ADD802443D5} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
{42C5D417-4060-48F4-BB28-E9E179007779} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
{4DA2D7C1-A7B6-4C01-B57D-89E6EA4609DE} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E}
{C651F432-4EBE-41A6-BAD2-3E07CCBA209C} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
{A19022EF-9BA3-4349-94E4-F48E13E1C8AE} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}

View File

@ -2,7 +2,7 @@
"description": "Features that enable globalization & localization of MVC applications.",
"version": "6.0.0-*",
"compilationOptions": {
"warningsAsErrors": false
"warningsAsErrors": true
},
"dependencies": {
"Microsoft.AspNet.Localization": "1.0.0-*",

View File

@ -3,13 +3,9 @@
using System;
using System.Collections.Generic;
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;
namespace Microsoft.AspNet.Mvc.Razor.Compilation
{
@ -18,46 +14,36 @@ namespace Microsoft.AspNet.Mvc.Razor.Compilation
/// </summary>
public class CompilerCache : ICompilerCache
{
private static readonly Assembly RazorHostAssembly = typeof(CompilerCache).GetTypeInfo().Assembly;
private readonly IFileProvider _fileProvider;
private readonly IMemoryCache _cache;
/// <summary>
/// Initializes a new instance of <see cref="CompilerCache"/> populated with precompiled views
/// discovered using <paramref name="provider"/>.
/// Initializes a new instance of <see cref="CompilerCache"/>.
/// </summary>
/// <param name="razorFileInfoCollections">The sequence of <see cref="RazorFileInfoCollection"/> that provides
/// information for precompiled view discovery.</param>
/// <param name="loaderContextAccessor">The <see cref="IAssemblyLoadContextAccessor"/>.</param>
/// <param name="optionsAccessor">An accessor to the <see cref="RazorViewEngineOptions"/>.</param>
public CompilerCache(
IEnumerable<RazorFileInfoCollection> razorFileInfoCollections,
IAssemblyLoadContextAccessor loadContextAccessor,
IOptions<RazorViewEngineOptions> optionsAccessor)
: this(razorFileInfoCollections,
loadContextAccessor.GetLoadContext(RazorHostAssembly),
optionsAccessor.Options.FileProvider)
{
}
internal CompilerCache(
IEnumerable<RazorFileInfoCollection> razorFileInfoCollections,
IAssemblyLoadContext loadContext,
IFileProvider fileProvider)
/// <param name="fileProvider"><see cref="IFileProvider"/> used to locate Razor views.</param>
public CompilerCache([NotNull] IFileProvider fileProvider)
{
_fileProvider = fileProvider;
_cache = new MemoryCache(new MemoryCacheOptions { CompactOnMemoryPressure = false });
}
foreach (var viewCollection in razorFileInfoCollections)
/// <summary>
/// Initializes a new instance of <see cref="CompilerCache"/> populated with precompiled views
/// specified by <paramref name="precompiledViews"/>.
/// </summary>
/// <param name="fileProvider"><see cref="IFileProvider"/> used to locate Razor views.</param>
/// <param name="precompiledViews">A mapping of application relative paths of view to the precompiled view
/// <see cref="Type"/>s.</param>
public CompilerCache(
[NotNull] IFileProvider fileProvider,
[NotNull] IDictionary<string, Type> precompiledViews)
: this(fileProvider)
{
foreach (var item in precompiledViews)
{
var containingAssembly = viewCollection.LoadAssembly(loadContext);
foreach (var fileInfo in viewCollection.FileInfos)
{
var viewType = containingAssembly.GetType(fileInfo.FullTypeName);
var cacheEntry = new CompilerCacheResult(CompilationResult.Successful(viewType));
var normalizedPath = NormalizePath(fileInfo.RelativePath);
_cache.Set(normalizedPath, cacheEntry);
}
var cacheEntry = new CompilerCacheResult(CompilationResult.Successful(item.Value));
var normalizedPath = NormalizePath(item.Key);
_cache.Set(normalizedPath, cacheEntry);
}
}

View File

@ -0,0 +1,26 @@
// 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.Framework.OptionsModel;
namespace Microsoft.AspNet.Mvc.Razor.Compilation
{
/// <summary>
/// Default implementation for <see cref="ICompilerCacheProvider"/>.
/// </summary>
public class DefaultCompilerCacheProvider : ICompilerCacheProvider
{
/// <summary>
/// Initializes a new instance of <see cref="DefaultCompilerCacheProvider"/>.
/// </summary>
/// <param name="optionsAccessor">An accessor to the <see cref="RazorViewEngineOptions"/>.</param>
public DefaultCompilerCacheProvider(IOptions<RazorViewEngineOptions> mvcViewOptions)
{
var fileProvider = mvcViewOptions.Options.FileProvider;
Cache = new CompilerCache(fileProvider);
}
/// <inheritdoc />
public ICompilerCache Cache { get; }
}
}

View File

@ -0,0 +1,16 @@
// 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.Compilation
{
/// <summary>
/// Provides access to a cached <see cref="ICompilerCache"/> instance.
/// </summary>
public interface ICompilerCacheProvider
{
/// <summary>
/// The cached <see cref="ICompilerCache"/> instance.
/// </summary>
ICompilerCache Cache { get; }
}
}

View File

@ -0,0 +1,109 @@
// 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;
using System.Reflection;
using System.Threading;
using Microsoft.AspNet.FileProviders;
using Microsoft.AspNet.Mvc.Razor.Precompilation;
using Microsoft.Dnx.Runtime;
using Microsoft.Framework.OptionsModel;
namespace Microsoft.AspNet.Mvc.Razor.Compilation
{
/// <summary>
/// An <see cref="ICompilerCacheProvider"/> that provides a <see cref="CompilerCache"/> instance
/// populated with precompiled views.
/// </summary>
public class PrecompiledViewsCompilerCacheProvider : ICompilerCacheProvider
{
private static readonly Assembly RazorAssembly = typeof(DefaultCompilerCacheProvider).GetTypeInfo().Assembly;
private static readonly Type RazorFileInfoCollectionType = typeof(RazorFileInfoCollection);
private readonly Func<ICompilerCache> _createCache;
private readonly IAssemblyLoadContextAccessor _loadContextAccessor;
private readonly IFileProvider _fileProvider;
private readonly Assembly[] _assemblies;
private object _cacheLock = new object();
private bool _cacheInitialized;
private ICompilerCache _compilerCache;
/// <summary>
/// Initializes a new instance of <see cref="DefaultCompilerCacheProvider"/>.
/// </summary>
/// <param name="loaderContextAccessor">The <see cref="IAssemblyLoadContextAccessor"/>.</param>
/// <param name="optionsAccessor">An accessor to the <see cref="RazorViewEngineOptions"/>.</param>
/// <param name="assemblies"><see cref="Assembly"/> instances to scan for precompiled views.</param>
public PrecompiledViewsCompilerCacheProvider(
IAssemblyLoadContextAccessor loadContextAccessor,
IOptions<RazorViewEngineOptions> mvcViewOptions,
IEnumerable<Assembly> assemblies)
{
_loadContextAccessor = loadContextAccessor;
_fileProvider = mvcViewOptions.Options.FileProvider;
_createCache = CreateCache;
_assemblies = assemblies.ToArray();
}
/// <inheritdoc />
public ICompilerCache Cache
{
get
{
return LazyInitializer.EnsureInitialized(
ref _compilerCache,
ref _cacheInitialized,
ref _cacheLock,
_createCache);
}
}
private ICompilerCache CreateCache()
{
var razorFileInfoCollections = GetFileInfoCollections(_assemblies);
var loadContext = _loadContextAccessor.GetLoadContext(RazorAssembly);
var precompiledViews = GetPrecompiledViews(razorFileInfoCollections, loadContext);
return new CompilerCache(_fileProvider, precompiledViews);
}
// Internal for unit testing
internal static Dictionary<string, Type> GetPrecompiledViews(
IEnumerable<RazorFileInfoCollection> razorFileInfoCollections,
IAssemblyLoadContext loadContext)
{
var precompiledViews = new Dictionary<string, Type>(StringComparer.OrdinalIgnoreCase);
foreach (var viewCollection in razorFileInfoCollections)
{
var containingAssembly = viewCollection.LoadAssembly(loadContext);
foreach (var fileInfo in viewCollection.FileInfos)
{
var viewType = containingAssembly.GetType(fileInfo.FullTypeName);
precompiledViews[fileInfo.RelativePath] = viewType;
}
}
return precompiledViews;
}
private static IEnumerable<RazorFileInfoCollection> GetFileInfoCollections(IEnumerable<Assembly> assemblies)
{
return assemblies
.SelectMany(assembly => assembly.ExportedTypes)
.Where(IsValidRazorFileInfoCollection)
.Select(Activator.CreateInstance)
.Cast<RazorFileInfoCollection>();
}
internal static bool IsValidRazorFileInfoCollection(Type type)
{
return
RazorFileInfoCollectionType.IsAssignableFrom(type) &&
!type.GetTypeInfo().IsAbstract &&
!type.GetTypeInfo().ContainsGenericParameters;
}
}
}

View File

@ -2,10 +2,11 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Linq;
using System.Reflection;
using Microsoft.AspNet.Mvc;
using Microsoft.AspNet.Mvc.Razor;
using Microsoft.AspNet.Mvc.Razor.Internal;
using Microsoft.AspNet.Mvc.Razor.Compilation;
using Microsoft.AspNet.Razor.Runtime.TagHelpers;
using Microsoft.Framework.DependencyInjection.Extensions;
using Microsoft.Framework.Internal;
@ -53,13 +54,17 @@ namespace Microsoft.Framework.DependencyInjection
return builder;
}
public static IMvcBuilder AddPrecompiledRazorViews(
[NotNull] this IMvcBuilder builder,
[NotNull] params Assembly[] assemblies)
{
var razorFileInfos = RazorFileInfoCollections.GetFileInfoCollections(assemblies);
builder.Services.TryAddEnumerable(ServiceDescriptor.Instance(razorFileInfos));
builder.Services.Replace(
ServiceDescriptor.Singleton<ICompilerCacheProvider>(serviceProvider =>
ActivatorUtilities.CreateInstance<PrecompiledViewsCompilerCacheProvider>(
serviceProvider,
assemblies.AsEnumerable())));
return builder;
}
}

View File

@ -2,12 +2,12 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
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.Internal;
using Microsoft.AspNet.Razor.Runtime.TagHelpers;
using Microsoft.Framework.Caching.Memory;
using Microsoft.Framework.DependencyInjection.Extensions;
@ -46,8 +46,11 @@ namespace Microsoft.Framework.DependencyInjection
{
AddRazorViewEngine(builder);
var razorFileInfos = RazorFileInfoCollections.GetFileInfoCollections(assemblies);
builder.Services.TryAddEnumerable(ServiceDescriptor.Instance(razorFileInfos));
builder.Services.Replace(
ServiceDescriptor.Singleton<ICompilerCacheProvider>(serviceProvider =>
ActivatorUtilities.CreateInstance<PrecompiledViewsCompilerCacheProvider>(
serviceProvider,
assemblies.AsEnumerable())));
return builder;
}
@ -97,7 +100,7 @@ namespace Microsoft.Framework.DependencyInjection
services.TryAddTransient<IMvcRazorHost, MvcRazorHost>();
// Caches compilation artifacts across the lifetime of the application.
services.TryAddSingleton<ICompilerCache, CompilerCache>();
services.TryAddSingleton<ICompilerCacheProvider, DefaultCompilerCacheProvider>();
// This caches compilation related details that are valid across the lifetime of the application
// and is required to be a singleton.

View File

@ -1,34 +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;
using System.Reflection;
using Microsoft.AspNet.Mvc.Razor.Precompilation;
namespace Microsoft.AspNet.Mvc.Razor.Internal
{
public static class RazorFileInfoCollections
{
private static readonly Type RazorFileInfoCollectionType = typeof(RazorFileInfoCollection);
public static IEnumerable<RazorFileInfoCollection> GetFileInfoCollections(IEnumerable<Assembly> assemblies)
{
return assemblies
.SelectMany(assembly => assembly.ExportedTypes)
.Where(IsValidRazorFileInfoCollection)
.Select(Activator.CreateInstance)
.Cast<RazorFileInfoCollection>();
}
public static bool IsValidRazorFileInfoCollection(Type type)
{
return
RazorFileInfoCollectionType.IsAssignableFrom(type) &&
!type.GetTypeInfo().IsAbstract &&
!type.GetTypeInfo().ContainsGenericParameters;
}
}
}

View File

@ -15,14 +15,21 @@ namespace Microsoft.AspNet.Mvc.Razor
public class VirtualPathRazorPageFactory : IRazorPageFactory
{
private readonly IServiceProvider _serviceProvider;
private readonly ICompilerCache _compilerCache;
private readonly ICompilerCacheProvider _compilerCacheProvider;
private IRazorCompilationService _razorcompilationService;
private ICompilerCache _compilerCache;
public VirtualPathRazorPageFactory(IServiceProvider serviceProvider,
ICompilerCache compilerCache)
/// <summary>
/// Initializes a new instance of <see cref="VirtualPathRazorPageFactory"/>.
/// </summary>
/// <param name="serviceProvider">The request specific <see cref="IServiceProvider"/>.</param>
/// <param name="compilerCacheProvider">The <see cref="ICompilerCacheProvider"/>.</param>
public VirtualPathRazorPageFactory(
IServiceProvider serviceProvider,
ICompilerCacheProvider compilerCacheProvider)
{
_serviceProvider = serviceProvider;
_compilerCache = compilerCache;
_compilerCacheProvider = compilerCacheProvider;
}
private IRazorCompilationService RazorCompilationService
@ -41,6 +48,19 @@ namespace Microsoft.AspNet.Mvc.Razor
}
}
private ICompilerCache CompilerCache
{
get
{
if (_compilerCache == null)
{
_compilerCache = _compilerCacheProvider.Cache;
}
return _compilerCache;
}
}
/// <inheritdoc />
public IRazorPage CreateInstance([NotNull] string relativePath)
{
@ -50,7 +70,7 @@ namespace Microsoft.AspNet.Mvc.Razor
relativePath = relativePath.Substring(1);
}
var result = _compilerCache.GetOrAdd(
var result = CompilerCache.GetOrAdd(
relativePath,
RazorCompilationService.Compile);

View File

@ -6,7 +6,7 @@
"url": "git://github.com/aspnet/mvc"
},
"compilationOptions": {
"warningsAsErrors": false
"warningsAsErrors": true
},
"dependencies": {
"Microsoft.AspNet.Mvc.Razor.Host": "6.0.0-*",

View File

@ -1,49 +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.Net;
using System.Threading.Tasks;
using Microsoft.AspNet.Builder;
using Microsoft.Framework.DependencyInjection;
using RazorCompilerCacheWebSite;
using Xunit;
namespace Microsoft.AspNet.Mvc.FunctionalTests
{
public class RazorCompilerCacheTest
{
private const string SiteName = nameof(RazorCompilerCacheWebSite);
private readonly Action<IApplicationBuilder> _app = new Startup().Configure;
private readonly Action<IServiceCollection> _configureServices = new Startup().ConfigureServices;
[Fact]
public async Task CompilerCache_IsNotInitializedUntilFirstViewRequest()
{
// Arrange
var server = TestHelper.CreateServer(_app, SiteName, _configureServices);
var client = server.CreateClient();
client.BaseAddress = new Uri("http://localhost");
// Act - 1
// Visit a sampling of controller actions that do not produce ViewResult
var result1 = await client.GetAsync("/file");
var result2 = await client.GetAsync("/statuscode");
var result3 = await client.GetStringAsync("/cache-status");
// Assert - 1
Assert.Equal(HttpStatusCode.OK, result1.StatusCode);
Assert.Equal(HttpStatusCode.OK, result2.StatusCode);
// Ensure the cache was not initialized.
Assert.Equal(bool.FalseString, result3);
// Act - 2
var result4 = await client.GetStringAsync("/view");
var result5 = await client.GetStringAsync("/cache-status");
// Assert - 2
Assert.Equal("Hello from view!", result4);
Assert.Equal(bool.TrueString, result5);
}
}
}

View File

@ -49,7 +49,6 @@
"ModelBindingWebSite": "1.0.0",
"MvcSample.Web": "1.0.0",
"PrecompilationWebSite": "1.0.0",
"RazorCompilerCacheWebSite": "1.0.0",
"RazorEmbeddedViewsWebSite": "1.0.0",
"RazorPageExecutionInstrumentationWebSite": "1.0.0",
"RazorWebSite": "1.0.0",

View File

@ -4,7 +4,6 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using Microsoft.AspNet.Mvc.Razor.Precompilation;
using Microsoft.Dnx.Runtime;
@ -17,7 +16,10 @@ namespace Microsoft.AspNet.Mvc.Razor.Compilation
{
private const string ViewPath = "Views/Home/Index.cshtml";
private const string PrecompiledViewsPath = "Views/Home/Precompiled.cshtml";
private readonly IAssemblyLoadContext TestLoadContext = Mock.Of<IAssemblyLoadContext>();
private readonly IDictionary<string, Type> _precompiledViews = new Dictionary<string, Type>
{
{ PrecompiledViewsPath, typeof(PreCompile) }
};
public static TheoryData ViewImportsPaths =>
new TheoryData<string>
@ -32,7 +34,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Compilation
{
// Arrange
var fileProvider = new TestFileProvider();
var cache = new CompilerCache(Enumerable.Empty<RazorFileInfoCollection>(), TestLoadContext, fileProvider);
var cache = new CompilerCache(fileProvider);
// Act
var result = cache.GetOrAdd("/some/path", ThrowsIfCalled);
@ -48,7 +50,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Compilation
// Arrange
var fileProvider = new TestFileProvider();
fileProvider.AddFile(ViewPath, "some content");
var cache = new CompilerCache(Enumerable.Empty<RazorFileInfoCollection>(), TestLoadContext, fileProvider);
var cache = new CompilerCache(fileProvider);
var type = typeof(TestView);
var expected = UncachedCompilationResult.Successful(type, "hello world");
@ -70,7 +72,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Compilation
// Arrange
var fileProvider = new TestFileProvider();
fileProvider.AddFile(ViewPath, "some content");
var cache = new CompilerCache(Enumerable.Empty<RazorFileInfoCollection>(), TestLoadContext, fileProvider);
var cache = new CompilerCache(fileProvider);
var type = typeof(TestView);
var expected = UncachedCompilationResult.Successful(type, "hello world");
@ -98,7 +100,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Compilation
// Arrange
var fileProvider = new TestFileProvider();
fileProvider.AddFile(ViewPath, "some content");
var cache = new CompilerCache(Enumerable.Empty<RazorFileInfoCollection>(), TestLoadContext, fileProvider);
var cache = new CompilerCache(fileProvider);
var expected1 = UncachedCompilationResult.Successful(typeof(TestView), "hello world");
var expected2 = UncachedCompilationResult.Successful(typeof(DifferentView), "different content");
@ -133,7 +135,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Compilation
// Arrange
var fileProvider = new TestFileProvider();
fileProvider.AddFile(ViewPath, "some content");
var cache = new CompilerCache(Enumerable.Empty<RazorFileInfoCollection>(), TestLoadContext, fileProvider);
var cache = new CompilerCache(fileProvider);
var expected1 = UncachedCompilationResult.Successful(typeof(TestView), "hello world");
var expected2 = UncachedCompilationResult.Successful(typeof(DifferentView), "different content");
@ -168,7 +170,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Compilation
var mockFileProvider = new Mock<TestFileProvider> { CallBase = true };
var fileProvider = mockFileProvider.Object;
fileProvider.AddFile(ViewPath, "some content");
var cache = new CompilerCache(Enumerable.Empty<RazorFileInfoCollection>(), TestLoadContext, fileProvider);
var cache = new CompilerCache(fileProvider);
var type = typeof(TestView);
var expected = UncachedCompilationResult.Successful(type, "hello world");
@ -194,7 +196,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Compilation
{
// Arrange
var fileProvider = new TestFileProvider();
var cache = new CompilerCache(new[] { new TestViewCollection() }, TestLoadContext, fileProvider);
var cache = new CompilerCache(fileProvider, _precompiledViews);
// Act
var result = cache.GetOrAdd(PrecompiledViewsPath, ThrowsIfCalled);
@ -209,7 +211,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Compilation
{
// Arrange
var fileProvider = new TestFileProvider();
var cache = new CompilerCache(new[] { new TestViewCollection() }, TestLoadContext, fileProvider);
var cache = new CompilerCache(fileProvider, _precompiledViews);
// Act
fileProvider.Watch(PrecompiledViewsPath);
@ -227,7 +229,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Compilation
{
// Arrange
var fileProvider = new TestFileProvider();
var cache = new CompilerCache(new[] { new TestViewCollection() }, TestLoadContext, fileProvider);
var cache = new CompilerCache(fileProvider, _precompiledViews);
// Act
fileProvider.Watch(globalImportPath);
@ -245,7 +247,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Compilation
// Arrange
var fileProvider = new TestFileProvider();
fileProvider.AddFile(ViewPath, "some content");
var cache = new CompilerCache(new[] { new TestViewCollection() }, TestLoadContext, fileProvider);
var cache = new CompilerCache(fileProvider, _precompiledViews);
var expected = CompilationResult.Successful(typeof(TestView));
// Act 1
@ -285,25 +287,5 @@ namespace Microsoft.AspNet.Mvc.Razor.Compilation
{
throw new Exception("Shouldn't be called");
}
private class TestViewCollection : RazorFileInfoCollection
{
public TestViewCollection()
{
FileInfos = new List<RazorFileInfo>
{
new RazorFileInfo
{
FullTypeName = typeof(PreCompile).FullName,
RelativePath = PrecompiledViewsPath,
}
};
}
public override Assembly LoadAssembly(IAssemblyLoadContext loadContext)
{
return typeof(TestViewCollection).Assembly;
}
}
}
}

View File

@ -0,0 +1,142 @@
// 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.Reflection;
using Microsoft.AspNet.Mvc.Razor.Precompilation;
using Microsoft.Dnx.Runtime;
using Moq;
using Xunit;
namespace Microsoft.AspNet.Mvc.Razor.Compilation
{
public class PrecompiledViewsCompilerCacheProviderTest
{
[Fact]
public void IsValidRazorFileInfoCollection_ReturnsFalse_IfTypeIsAbstract()
{
// Arrange
var type = typeof(AbstractRazorFileInfoCollection);
// Act
var result = PrecompiledViewsCompilerCacheProvider.IsValidRazorFileInfoCollection(type);
// Assert
Assert.False(result);
}
[Fact]
public void IsValidRazorFileInfoCollection_ReturnsFalse_IfTypeHasGenericParameters()
{
// Arrange
var type = typeof(GenericRazorFileInfoCollection<>);
// Act
var result = PrecompiledViewsCompilerCacheProvider.IsValidRazorFileInfoCollection(type);
// Assert
Assert.False(result);
}
[Fact]
public void IsValidRazorFileInfoCollection_ReturnsFalse_IfTypeDoesNotDeriveFromRazorFileInfoCollection()
{
// Arrange
var type = typeof(NonSubTypeRazorFileInfoCollection);
// Act
var result = PrecompiledViewsCompilerCacheProvider.IsValidRazorFileInfoCollection(type);
// Assert
Assert.False(result);
}
[Theory]
[InlineData(typeof(ParameterConstructorRazorFileInfoCollection))]
[InlineData(typeof(ViewCollection))]
public void IsValidRazorFileInfoCollection_ReturnsTrue_IfTypeDerivesFromRazorFileInfoCollection(Type type)
{
// Act
var result = PrecompiledViewsCompilerCacheProvider.IsValidRazorFileInfoCollection(type);
// Assert
Assert.True(result);
}
[Fact]
public void GetPrecompiledViews_ReturnsTypesSpecifiedByRazorFileInfoCollections()
{
// Arrange
var fileInfoCollections = new[] { new ViewCollection() };
// Act
var precompiledViews = PrecompiledViewsCompilerCacheProvider.GetPrecompiledViews(
fileInfoCollections,
Mock.Of<IAssemblyLoadContext>());
// Assert
Assert.Equal(2, precompiledViews.Count);
Type type;
Assert.True(precompiledViews.TryGetValue("Views/Home/Index.cshtml", out type));
Assert.Equal(typeof(TestView1), type);
Assert.True(precompiledViews.TryGetValue("Views/Home/About.cshtml", out type));
Assert.Equal(typeof(TestView2), type);
}
private abstract class AbstractRazorFileInfoCollection : RazorFileInfoCollection
{
}
private class GenericRazorFileInfoCollection<TVal> : RazorFileInfoCollection
{
}
private class ParameterConstructorRazorFileInfoCollection : RazorFileInfoCollection
{
public ParameterConstructorRazorFileInfoCollection(string value)
{
}
}
private class NonSubTypeRazorFileInfoCollection : Controller
{
}
private class ViewCollection : RazorFileInfoCollection
{
public ViewCollection()
{
FileInfos = new[]
{
new RazorFileInfo
{
FullTypeName = typeof(TestView1).FullName,
RelativePath = "Views/Home/Index.cshtml"
},
new RazorFileInfo
{
FullTypeName = typeof(TestView2).FullName,
RelativePath = "Views/Home/About.cshtml"
},
};
}
public override Assembly LoadAssembly(IAssemblyLoadContext loadContext)
{
return GetType().GetTypeInfo().Assembly;
}
}
private class TestView1
{
}
private class TestView2
{
}
}
}

View File

@ -1,88 +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;
using Microsoft.AspNet.Mvc.Razor.Internal;
using Microsoft.AspNet.Mvc.Razor.Precompilation;
using Xunit;
namespace Microsoft.Framework.DependencyInjection
{
public class RazorFileInfoCollectionsTest
{
[Fact]
public void IsValidRazorFileInfoCollection_ReturnsFalse_IfTypeIsAbstract()
{
// Arrange
var type = typeof(AbstractRazorFileInfoCollection);
// Act
var result = RazorFileInfoCollections.IsValidRazorFileInfoCollection(type);
// Assert
Assert.False(result);
}
[Fact]
public void IsValidRazorFileInfoCollection_ReturnsFalse_IfTypeHasGenericParameters()
{
// Arrange
var type = typeof(GenericRazorFileInfoCollection<>);
// Act
var result = RazorFileInfoCollections.IsValidRazorFileInfoCollection(type);
// Assert
Assert.False(result);
}
[Fact]
public void IsValidRazorFileInfoCollection_ReturnsFalse_IfTypeDoesNotDeriveFromRazorFileInfoCollection()
{
// Arrange
var type = typeof(NonSubTypeRazorFileInfoCollection);
// Act
var result = RazorFileInfoCollections.IsValidRazorFileInfoCollection(type);
// Assert
Assert.False(result);
}
[Theory]
[InlineData(typeof(ParameterConstructorRazorFileInfoCollection))]
[InlineData(typeof(ViewCollection))]
public void IsValidRazorFileInfoCollection_ReturnsTrue_IfTypeDerivesFromRazorFileInfoCollection(Type type)
{
// Act
var result = RazorFileInfoCollections.IsValidRazorFileInfoCollection(type);
// Assert
Assert.True(result);
}
private abstract class AbstractRazorFileInfoCollection : RazorFileInfoCollection
{
}
private class GenericRazorFileInfoCollection<TVal> : RazorFileInfoCollection
{
}
private class ParameterConstructorRazorFileInfoCollection : RazorFileInfoCollection
{
public ParameterConstructorRazorFileInfoCollection(string value)
{
}
}
private class NonSubTypeRazorFileInfoCollection : Controller
{
}
private class ViewCollection : RazorFileInfoCollection
{
}
}
}

View File

@ -1,36 +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;
namespace RazorCompilerCacheWebSite
{
public class CompilerCacheController : Controller
{
[HttpGet("/cache-status")]
[Produces("text/plain")]
public ContentResult GetCompilerCacheInitializationStatus(
[FromServices] CompilerCacheInitialiedService service)
{
return Content(service.Initialized.ToString());
}
[HttpGet("/statuscode")]
public IActionResult StatusCodeAction()
{
return new EmptyResult();
}
[HttpGet("/file")]
public IActionResult FileAction()
{
return File("HelloWorld.htm", "application/text");
}
[HttpGet("/view")]
public ViewResult Index()
{
return View("~/Index");
}
}
}

View File

@ -1 +0,0 @@
Hello from view!

View File

@ -1,18 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<ProjectGuid>42c5d417-4060-48f4-bb28-e9e179007779</ProjectGuid>
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">..\..\..\artifacts\obj\$(MSBuildProjectName)</BaseIntermediateOutputPath>
<OutputPath Condition="'$(OutputPath)'=='' ">..\..\..\artifacts\bin\$(MSBuildProjectName)\</OutputPath>
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
<DevelopmentServerPort>42473</DevelopmentServerPort>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>

View File

@ -1,10 +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 RazorCompilerCacheWebSite
{
public class CompilerCacheInitialiedService
{
public bool Initialized { get; set; }
}
}

View File

@ -1,24 +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;
using Microsoft.AspNet.Mvc.Razor;
using Microsoft.AspNet.Mvc.Razor.Compilation;
using Microsoft.AspNet.Mvc.Razor.Precompilation;
using Microsoft.Dnx.Runtime;
using Microsoft.Framework.OptionsModel;
namespace RazorCompilerCacheWebSite
{
public class CustomCompilerCache : CompilerCache
{
public CustomCompilerCache(IEnumerable<RazorFileInfoCollection> fileInfoCollection,
IAssemblyLoadContextAccessor loadContextAccessor,
IOptions<RazorViewEngineOptions> optionsAccessor,
CompilerCacheInitialiedService cacheInitializedService)
: base(fileInfoCollection, loadContextAccessor, optionsAccessor)
{
cacheInitializedService.Initialized = true;
}
}
}

View File

@ -1,28 +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.Builder;
using Microsoft.AspNet.Mvc.Razor.Compilation;
using Microsoft.Framework.DependencyInjection;
namespace RazorCompilerCacheWebSite
{
public class Startup
{
// Set up application services
public void ConfigureServices(IServiceCollection services)
{
// Add MVC services to the services container
services.AddMvc();
services.AddSingleton<ICompilerCache, CustomCompilerCache>();
services.AddSingleton<CompilerCacheInitialiedService>();
}
public void Configure(IApplicationBuilder app)
{
app.UseCultureReplacer();
app.UseMvc();
}
}
}

View File

@ -1,19 +0,0 @@
{
"commands": {
"web": "Microsoft.AspNet.Hosting server=Microsoft.AspNet.Server.WebListener server.urls=http://localhost:5001",
"kestrel": "Microsoft.AspNet.Hosting --server Microsoft.AspNet.Server.Kestrel --server.urls http://localhost:5000"
},
"dependencies": {
"Microsoft.AspNet.Server.Kestrel": "1.0.0-*",
"Microsoft.AspNet.Mvc": "6.0.0-*",
"Microsoft.AspNet.Mvc.TestConfiguration": "1.0.0",
"Microsoft.AspNet.Server.IIS": "1.0.0-*",
"Microsoft.AspNet.Server.WebListener": "1.0.0-*",
"Microsoft.AspNet.StaticFiles": "1.0.0-*"
},
"frameworks": {
"dnx451": { },
"dnxcore50": { }
},
"webroot": "wwwroot"
}

View File

@ -1 +0,0 @@
A functional test website to verify that the Razor compiler cache gets lazily initialized.