diff options
author | Pranav K <prkrishn@hotmail.com> | 2021-10-27 00:29:34 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-10-27 00:29:34 +0300 |
commit | ae1a6cbe225b99c0bf38b7e31bf60cb653b73a52 (patch) | |
tree | 1aab32f6b5306c8fddc95c20b574ece0c0a70979 | |
parent | 7c57ecbdfee67ad544bd655a757e03145f6122a2 (diff) |
Ensure view caches are cleared when RazorHotReload.ClearCache is invoked (#37854)v6.0.0
7 files changed, 96 insertions, 8 deletions
diff --git a/src/Mvc/Mvc.Razor/src/Compilation/DefaultViewCompiler.cs b/src/Mvc/Mvc.Razor/src/Compilation/DefaultViewCompiler.cs index 872692b11b..932320b784 100644 --- a/src/Mvc/Mvc.Razor/src/Compilation/DefaultViewCompiler.cs +++ b/src/Mvc/Mvc.Razor/src/Compilation/DefaultViewCompiler.cs @@ -74,6 +74,8 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Compilation _compiledViews = compiledViews; } + internal Dictionary<string, Task<CompiledViewDescriptor>>? CompiledViews => _compiledViews; + // Invoked as part of a hot reload event. internal void ClearCache() { diff --git a/src/Mvc/Mvc.Razor/src/Compilation/DefaultViewCompilerProvider.cs b/src/Mvc/Mvc.Razor/src/Compilation/DefaultViewCompilerProvider.cs index eee0073796..2a16ce1e1b 100644 --- a/src/Mvc/Mvc.Razor/src/Compilation/DefaultViewCompilerProvider.cs +++ b/src/Mvc/Mvc.Razor/src/Compilation/DefaultViewCompilerProvider.cs @@ -18,6 +18,8 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Compilation _compiler = new DefaultViewCompiler(applicationPartManager, loggerFactory.CreateLogger<DefaultViewCompiler>()); } + internal DefaultViewCompiler Compiler => _compiler; + public IViewCompiler GetCompiler() => _compiler; } } diff --git a/src/Mvc/Mvc.Razor/src/RazorHotReload.cs b/src/Mvc/Mvc.Razor/src/RazorHotReload.cs index 6efeb0ac5d..d77bbae8eb 100644 --- a/src/Mvc/Mvc.Razor/src/RazorHotReload.cs +++ b/src/Mvc/Mvc.Razor/src/RazorHotReload.cs @@ -28,9 +28,9 @@ namespace Microsoft.AspNetCore.Mvc.Razor // For Razor view services, use the service locator pattern because they views not be registered by default. _razorCompiledItemFeatureProvider = applicationPartManager.FeatureProviders.OfType<RazorCompiledItemFeatureProvider>().FirstOrDefault(); - if (viewCompilerProvider is DefaultViewCompiler defaultViewCompiler) + if (viewCompilerProvider is DefaultViewCompilerProvider defaultViewCompilerProvider) { - _defaultViewCompiler = defaultViewCompiler; + _defaultViewCompiler = defaultViewCompilerProvider.Compiler; } if (razorViewEngine.GetType() == typeof(RazorViewEngine)) diff --git a/src/Mvc/Mvc.Razor/src/RazorPageActivator.cs b/src/Mvc/Mvc.Razor/src/RazorPageActivator.cs index 064aaf636b..b250d1f43f 100644 --- a/src/Mvc/Mvc.Razor/src/RazorPageActivator.cs +++ b/src/Mvc/Mvc.Razor/src/RazorPageActivator.cs @@ -53,6 +53,8 @@ namespace Microsoft.AspNetCore.Mvc.Razor _activationInfo.Clear(); } + internal ConcurrentDictionary<CacheKey, RazorPagePropertyActivator> ActivationInfo => _activationInfo; + /// <inheritdoc /> public void Activate(IRazorPage page, ViewContext context) { @@ -107,7 +109,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor return propertyActivator; } - private readonly struct CacheKey : IEquatable<CacheKey> + internal readonly struct CacheKey : IEquatable<CacheKey> { public CacheKey(Type pageType, Type? providedModelType) { diff --git a/src/Mvc/Mvc.Razor/src/RazorViewEngine.cs b/src/Mvc/Mvc.Razor/src/RazorViewEngine.cs index 0d225bb9ed..99b3288712 100644 --- a/src/Mvc/Mvc.Razor/src/RazorViewEngine.cs +++ b/src/Mvc/Mvc.Razor/src/RazorViewEngine.cs @@ -89,7 +89,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor /// <summary> /// A cache for results of view lookups. /// </summary> - protected IMemoryCache ViewLookupCache { get; private set; } + protected internal IMemoryCache ViewLookupCache { get; private set; } /// <summary> /// Gets the case-normalized route value for the specified route <paramref name="key"/>. diff --git a/src/Mvc/Mvc.Razor/test/Compilation/DefaultViewCompilerTest.cs b/src/Mvc/Mvc.Razor/test/Compilation/DefaultViewCompilerTest.cs index 9fc7a75199..ea227db36c 100644 --- a/src/Mvc/Mvc.Razor/test/Compilation/DefaultViewCompilerTest.cs +++ b/src/Mvc/Mvc.Razor/test/Compilation/DefaultViewCompilerTest.cs @@ -1,12 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; -using System.Collections.Generic; -using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc.ApplicationParts; using Microsoft.Extensions.Logging.Abstractions; -using Xunit; namespace Microsoft.AspNetCore.Mvc.Razor.Compilation { diff --git a/src/Mvc/Mvc.Razor/test/RazorHotReloadTest.cs b/src/Mvc/Mvc.Razor/test/RazorHotReloadTest.cs new file mode 100644 index 0000000000..de45a3ce06 --- /dev/null +++ b/src/Mvc/Mvc.Razor/test/RazorHotReloadTest.cs @@ -0,0 +1,86 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.AspNetCore.Mvc.Razor.Compilation; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; + +namespace Microsoft.AspNetCore.Mvc.Razor; + +public class RazorHotReloadTest +{ + [Fact] + public void ClearCache_CanClearViewCompiler() + { + // Regression test for https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1425693 + // Arrange + var serviceProvider = GetServiceProvider(); + + var compilerProvider = Assert.IsType<DefaultViewCompilerProvider>(serviceProvider.GetRequiredService<IViewCompilerProvider>()); + var hotReload = serviceProvider.GetRequiredService<RazorHotReload>(); + + // Act + hotReload.ClearCache(Type.EmptyTypes); + + // Assert + Assert.Null(compilerProvider.Compiler.CompiledViews); + } + + [Fact] + public void ClearCache_ResetsViewEngineLookupCache() + { + // Arrange + var serviceProvider = GetServiceProvider(); + + var viewEngine = Assert.IsType<RazorViewEngine>(serviceProvider.GetRequiredService<IRazorViewEngine>()); + var hotReload = serviceProvider.GetRequiredService<RazorHotReload>(); + var lookup = viewEngine.ViewLookupCache; + + // Act + hotReload.ClearCache(Type.EmptyTypes); + + // Assert + Assert.NotSame(lookup, viewEngine.ViewLookupCache); + } + + [Fact] + public void ClearCache_ResetsRazorPageActivator() + { + // Arrange + var serviceProvider = GetServiceProvider(); + + var pageActivator = Assert.IsType<RazorPageActivator>(serviceProvider.GetRequiredService<IRazorPageActivator>()); + var hotReload = serviceProvider.GetRequiredService<RazorHotReload>(); + var cache = pageActivator.ActivationInfo; + cache[new RazorPageActivator.CacheKey()] = new RazorPagePropertyActivator( + typeof(string), typeof(object), + new EmptyModelMetadataProvider(), + new RazorPagePropertyActivator.PropertyValueAccessors()); + + // Act + Assert.Single(pageActivator.ActivationInfo); + hotReload.ClearCache(Type.EmptyTypes); + + // Assert + Assert.Empty(pageActivator.ActivationInfo); + } + + private static ServiceProvider GetServiceProvider() + { + var diagnosticListener = new DiagnosticListener("Microsoft.AspNetCore"); + + var serviceProvider = new ServiceCollection() + .AddControllersWithViews() + .Services + // Manually add RazorHotReload because it's only added if MetadataUpdateHandler.IsSupported = true + .AddSingleton<RazorHotReload>() + .AddSingleton<ILoggerFactory>(NullLoggerFactory.Instance) + .AddSingleton<DiagnosticSource>(diagnosticListener) + .AddSingleton(diagnosticListener) + .BuildServiceProvider(); + return serviceProvider; + } +} |