Skip to content

Commit ae1a6cb

Browse files
authored
Ensure view caches are cleared when RazorHotReload.ClearCache is invoked (#37854)
1 parent 7c57ecb commit ae1a6cb

File tree

7 files changed

+96
-8
lines changed

7 files changed

+96
-8
lines changed

src/Mvc/Mvc.Razor/src/Compilation/DefaultViewCompiler.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@ private void EnsureCompiledViews(ILogger logger)
7474
_compiledViews = compiledViews;
7575
}
7676

77+
internal Dictionary<string, Task<CompiledViewDescriptor>>? CompiledViews => _compiledViews;
78+
7779
// Invoked as part of a hot reload event.
7880
internal void ClearCache()
7981
{

src/Mvc/Mvc.Razor/src/Compilation/DefaultViewCompilerProvider.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ public DefaultViewCompilerProvider(
1818
_compiler = new DefaultViewCompiler(applicationPartManager, loggerFactory.CreateLogger<DefaultViewCompiler>());
1919
}
2020

21+
internal DefaultViewCompiler Compiler => _compiler;
22+
2123
public IViewCompiler GetCompiler() => _compiler;
2224
}
2325
}

src/Mvc/Mvc.Razor/src/RazorHotReload.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,9 @@ public RazorHotReload(
2828
// For Razor view services, use the service locator pattern because they views not be registered by default.
2929
_razorCompiledItemFeatureProvider = applicationPartManager.FeatureProviders.OfType<RazorCompiledItemFeatureProvider>().FirstOrDefault();
3030

31-
if (viewCompilerProvider is DefaultViewCompiler defaultViewCompiler)
31+
if (viewCompilerProvider is DefaultViewCompilerProvider defaultViewCompilerProvider)
3232
{
33-
_defaultViewCompiler = defaultViewCompiler;
33+
_defaultViewCompiler = defaultViewCompilerProvider.Compiler;
3434
}
3535

3636
if (razorViewEngine.GetType() == typeof(RazorViewEngine))

src/Mvc/Mvc.Razor/src/RazorPageActivator.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ internal void ClearCache()
5353
_activationInfo.Clear();
5454
}
5555

56+
internal ConcurrentDictionary<CacheKey, RazorPagePropertyActivator> ActivationInfo => _activationInfo;
57+
5658
/// <inheritdoc />
5759
public void Activate(IRazorPage page, ViewContext context)
5860
{
@@ -107,7 +109,7 @@ internal RazorPagePropertyActivator GetOrAddCacheEntry(IRazorPage page)
107109
return propertyActivator;
108110
}
109111

110-
private readonly struct CacheKey : IEquatable<CacheKey>
112+
internal readonly struct CacheKey : IEquatable<CacheKey>
111113
{
112114
public CacheKey(Type pageType, Type? providedModelType)
113115
{

src/Mvc/Mvc.Razor/src/RazorViewEngine.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ internal void ClearCache()
8989
/// <summary>
9090
/// A cache for results of view lookups.
9191
/// </summary>
92-
protected IMemoryCache ViewLookupCache { get; private set; }
92+
protected internal IMemoryCache ViewLookupCache { get; private set; }
9393

9494
/// <summary>
9595
/// Gets the case-normalized route value for the specified route <paramref name="key"/>.

src/Mvc/Mvc.Razor/test/Compilation/DefaultViewCompilerTest.cs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,8 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4-
using System;
5-
using System.Collections.Generic;
6-
using System.Threading.Tasks;
74
using Microsoft.AspNetCore.Mvc.ApplicationParts;
85
using Microsoft.Extensions.Logging.Abstractions;
9-
using Xunit;
106

117
namespace Microsoft.AspNetCore.Mvc.Razor.Compilation
128
{
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Diagnostics;
5+
using Microsoft.AspNetCore.Mvc.ModelBinding;
6+
using Microsoft.AspNetCore.Mvc.Razor.Compilation;
7+
using Microsoft.Extensions.DependencyInjection;
8+
using Microsoft.Extensions.Logging;
9+
using Microsoft.Extensions.Logging.Abstractions;
10+
11+
namespace Microsoft.AspNetCore.Mvc.Razor;
12+
13+
public class RazorHotReloadTest
14+
{
15+
[Fact]
16+
public void ClearCache_CanClearViewCompiler()
17+
{
18+
// Regression test for https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1425693
19+
// Arrange
20+
var serviceProvider = GetServiceProvider();
21+
22+
var compilerProvider = Assert.IsType<DefaultViewCompilerProvider>(serviceProvider.GetRequiredService<IViewCompilerProvider>());
23+
var hotReload = serviceProvider.GetRequiredService<RazorHotReload>();
24+
25+
// Act
26+
hotReload.ClearCache(Type.EmptyTypes);
27+
28+
// Assert
29+
Assert.Null(compilerProvider.Compiler.CompiledViews);
30+
}
31+
32+
[Fact]
33+
public void ClearCache_ResetsViewEngineLookupCache()
34+
{
35+
// Arrange
36+
var serviceProvider = GetServiceProvider();
37+
38+
var viewEngine = Assert.IsType<RazorViewEngine>(serviceProvider.GetRequiredService<IRazorViewEngine>());
39+
var hotReload = serviceProvider.GetRequiredService<RazorHotReload>();
40+
var lookup = viewEngine.ViewLookupCache;
41+
42+
// Act
43+
hotReload.ClearCache(Type.EmptyTypes);
44+
45+
// Assert
46+
Assert.NotSame(lookup, viewEngine.ViewLookupCache);
47+
}
48+
49+
[Fact]
50+
public void ClearCache_ResetsRazorPageActivator()
51+
{
52+
// Arrange
53+
var serviceProvider = GetServiceProvider();
54+
55+
var pageActivator = Assert.IsType<RazorPageActivator>(serviceProvider.GetRequiredService<IRazorPageActivator>());
56+
var hotReload = serviceProvider.GetRequiredService<RazorHotReload>();
57+
var cache = pageActivator.ActivationInfo;
58+
cache[new RazorPageActivator.CacheKey()] = new RazorPagePropertyActivator(
59+
typeof(string), typeof(object),
60+
new EmptyModelMetadataProvider(),
61+
new RazorPagePropertyActivator.PropertyValueAccessors());
62+
63+
// Act
64+
Assert.Single(pageActivator.ActivationInfo);
65+
hotReload.ClearCache(Type.EmptyTypes);
66+
67+
// Assert
68+
Assert.Empty(pageActivator.ActivationInfo);
69+
}
70+
71+
private static ServiceProvider GetServiceProvider()
72+
{
73+
var diagnosticListener = new DiagnosticListener("Microsoft.AspNetCore");
74+
75+
var serviceProvider = new ServiceCollection()
76+
.AddControllersWithViews()
77+
.Services
78+
// Manually add RazorHotReload because it's only added if MetadataUpdateHandler.IsSupported = true
79+
.AddSingleton<RazorHotReload>()
80+
.AddSingleton<ILoggerFactory>(NullLoggerFactory.Instance)
81+
.AddSingleton<DiagnosticSource>(diagnosticListener)
82+
.AddSingleton(diagnosticListener)
83+
.BuildServiceProvider();
84+
return serviceProvider;
85+
}
86+
}

0 commit comments

Comments
 (0)