Skip to content

Commit 63fb269

Browse files
authored
Clear some caches from MVC on hot reload event (#33386)
* Trigger action descriptor provider on hot reload. * Clear other caches that are keyed off of Type * Clean up MvcSandbox
1 parent 9fd548f commit 63fb269

File tree

18 files changed

+102
-191
lines changed

18 files changed

+102
-191
lines changed

src/Components/Web.JS/dist/Release/blazor.server.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Mvc/Mvc.Core/src/Controllers/DefaultControllerPropertyActivator.cs

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,27 +5,19 @@
55
using System.Collections.Concurrent;
66
using System.Collections.Generic;
77
using System.Linq;
8-
using System.Threading;
98
using Microsoft.AspNetCore.Mvc.Core;
109
using Microsoft.Extensions.Internal;
1110

1211
namespace Microsoft.AspNetCore.Mvc.Controllers
1312
{
14-
internal class DefaultControllerPropertyActivator : IControllerPropertyActivator
13+
internal sealed class DefaultControllerPropertyActivator : IControllerPropertyActivator
1514
{
1615
private static readonly Func<Type, PropertyActivator<ControllerContext>[]> _getPropertiesToActivate =
1716
GetPropertiesToActivate;
18-
private object _initializeLock = new object();
19-
private bool _initialized;
20-
private ConcurrentDictionary<Type, PropertyActivator<ControllerContext>[]>? _activateActions;
17+
private readonly ConcurrentDictionary<Type, PropertyActivator<ControllerContext>[]> _activateActions = new();
2118

2219
public void Activate(ControllerContext context, object controller)
2320
{
24-
LazyInitializer.EnsureInitialized(
25-
ref _activateActions,
26-
ref _initialized,
27-
ref _initializeLock);
28-
2921
var controllerType = controller.GetType();
3022
var propertiesToActivate = _activateActions!.GetOrAdd(
3123
controllerType,
@@ -38,6 +30,8 @@ public void Activate(ControllerContext context, object controller)
3830
}
3931
}
4032

33+
public void ClearCache() => _activateActions.Clear();
34+
4135
public Action<ControllerContext, object> GetActivatorDelegate(ControllerActionDescriptor actionDescriptor)
4236
{
4337
if (actionDescriptor == null)

src/Mvc/Mvc.Core/src/DependencyInjection/MvcCoreServiceCollectionExtensions.cs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,15 @@
1212
using Microsoft.AspNetCore.Mvc.ApplicationParts;
1313
using Microsoft.AspNetCore.Mvc.Controllers;
1414
using Microsoft.AspNetCore.Mvc.Filters;
15+
using Microsoft.AspNetCore.Mvc.HotReload;
1516
using Microsoft.AspNetCore.Mvc.Infrastructure;
1617
using Microsoft.AspNetCore.Mvc.ModelBinding;
1718
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
1819
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
1920
using Microsoft.AspNetCore.Mvc.Routing;
2021
using Microsoft.AspNetCore.Routing;
2122
using Microsoft.Extensions.DependencyInjection.Extensions;
23+
using Microsoft.Extensions.Hosting;
2224
using Microsoft.Extensions.Options;
2325

2426
namespace Microsoft.Extensions.DependencyInjection
@@ -51,13 +53,20 @@ public static IMvcCoreBuilder AddMvcCore(this IServiceCollection services)
5153
throw new ArgumentNullException(nameof(services));
5254
}
5355

54-
var partManager = GetApplicationPartManager(services);
56+
var environment = GetServiceFromCollection<IWebHostEnvironment>(services);
57+
var partManager = GetApplicationPartManager(services, environment);
5558
services.TryAddSingleton(partManager);
5659

5760
ConfigureDefaultFeatureProviders(partManager);
5861
ConfigureDefaultServices(services);
5962
AddMvcCoreServices(services);
6063

64+
if (environment?.IsDevelopment() ?? false)
65+
{
66+
services.TryAddEnumerable(
67+
ServiceDescriptor.Singleton<IActionDescriptorChangeProvider, HotReloadService>());
68+
}
69+
6170
var builder = new MvcCoreBuilder(services, partManager);
6271

6372
return builder;
@@ -71,14 +80,13 @@ private static void ConfigureDefaultFeatureProviders(ApplicationPartManager mana
7180
}
7281
}
7382

74-
private static ApplicationPartManager GetApplicationPartManager(IServiceCollection services)
83+
private static ApplicationPartManager GetApplicationPartManager(IServiceCollection services, IWebHostEnvironment? environment)
7584
{
7685
var manager = GetServiceFromCollection<ApplicationPartManager>(services);
7786
if (manager == null)
7887
{
7988
manager = new ApplicationPartManager();
8089

81-
var environment = GetServiceFromCollection<IWebHostEnvironment>(services);
8290
var entryAssemblyName = environment?.ApplicationName;
8391
if (string.IsNullOrEmpty(entryAssemblyName))
8492
{
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Reflection.Metadata;
6+
using System.Threading;
7+
using Microsoft.AspNetCore.Mvc.Controllers;
8+
using Microsoft.AspNetCore.Mvc.Infrastructure;
9+
using Microsoft.AspNetCore.Mvc.ModelBinding;
10+
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
11+
using Microsoft.Extensions.Primitives;
12+
13+
[assembly: MetadataUpdateHandler(typeof(Microsoft.AspNetCore.Mvc.HotReload.HotReloadService))]
14+
15+
namespace Microsoft.AspNetCore.Mvc.HotReload
16+
{
17+
internal sealed class HotReloadService : IActionDescriptorChangeProvider, IDisposable
18+
{
19+
private readonly DefaultModelMetadataProvider? _modelMetadataProvider;
20+
private readonly DefaultControllerPropertyActivator? _controllerPropertyActivator;
21+
private CancellationTokenSource _tokenSource = new();
22+
23+
public HotReloadService(
24+
IModelMetadataProvider modelMetadataProvider,
25+
IControllerPropertyActivator controllerPropertyActivator)
26+
{
27+
ClearCacheEvent += NotifyClearCache;
28+
if (modelMetadataProvider.GetType() == typeof(DefaultModelMetadataProvider))
29+
{
30+
_modelMetadataProvider = (DefaultModelMetadataProvider)modelMetadataProvider;
31+
}
32+
33+
if (controllerPropertyActivator is DefaultControllerPropertyActivator defaultControllerPropertyActivator)
34+
{
35+
_controllerPropertyActivator = defaultControllerPropertyActivator;
36+
}
37+
}
38+
39+
public static event Action? ClearCacheEvent;
40+
41+
public static void ClearCache(Type[]? _)
42+
{
43+
ClearCacheEvent?.Invoke();
44+
}
45+
46+
IChangeToken IActionDescriptorChangeProvider.GetChangeToken() => new CancellationChangeToken(_tokenSource.Token);
47+
48+
private void NotifyClearCache()
49+
{
50+
// Trigger the ActionDescriptorChangeProvider
51+
var current = Interlocked.Exchange(ref _tokenSource, new CancellationTokenSource());
52+
current.Cancel();
53+
54+
// Clear individual caches
55+
_modelMetadataProvider?.ClearCache();
56+
_controllerPropertyActivator?.ClearCache();
57+
}
58+
59+
public void Dispose()
60+
{
61+
ClearCacheEvent -= NotifyClearCache;
62+
}
63+
}
64+
}

src/Mvc/Mvc.Core/src/ModelBinding/Metadata/DefaultModelMetadataProvider.cs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
1919
/// </summary>
2020
public class DefaultModelMetadataProvider : ModelMetadataProvider
2121
{
22-
private readonly ModelMetadataCache _modelMetadataCache = new ModelMetadataCache();
22+
private readonly ConcurrentDictionary<ModelMetadataIdentity, ModelMetadataCacheEntry> _modelMetadataCache = new();
2323
private readonly Func<ModelMetadataIdentity, ModelMetadataCacheEntry> _cacheEntryFactory;
2424
private readonly ModelMetadataCacheEntry _metadataCacheEntryForObjectType;
2525

@@ -71,6 +71,8 @@ private DefaultModelMetadataProvider(
7171
/// <value>Same as <see cref="MvcOptions.ModelBindingMessageProvider"/> in all production scenarios.</value>
7272
protected DefaultModelBindingMessageProvider ModelBindingMessageProvider { get; }
7373

74+
internal void ClearCache() => _modelMetadataCache.Clear();
75+
7476
/// <inheritdoc />
7577
public override IEnumerable<ModelMetadata> GetMetadataForProperties(Type modelType)
7678
{
@@ -442,10 +444,6 @@ protected virtual DefaultMetadataDetails CreateParameterDetails(ModelMetadataIde
442444
ModelAttributes.GetAttributesForParameter(key.ParameterInfo!, key.ModelType));
443445
}
444446

445-
private class ModelMetadataCache : ConcurrentDictionary<ModelMetadataIdentity, ModelMetadataCacheEntry>
446-
{
447-
}
448-
449447
private readonly struct ModelMetadataCacheEntry
450448
{
451449
public ModelMetadataCacheEntry(ModelMetadata metadata, DefaultMetadataDetails details)

src/Mvc/Mvc.Core/test/DependencyInjection/MvcCoreServiceCollectionExtensionsTest.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ public void AddMvcCore_UsesOriginalHostingEnvironment()
152152
{
153153
// Arrange
154154
var services = new ServiceCollection();
155-
var environment = new Mock<IWebHostEnvironment>(MockBehavior.Strict);
155+
var environment = new Mock<IWebHostEnvironment>();
156156
environment.SetupGet(e => e.ApplicationName).Returns((string)null).Verifiable();
157157
services.AddSingleton<IWebHostEnvironment>(environment.Object);
158158

@@ -173,10 +173,10 @@ public void AddMvcCore_UsesLastHostingEnvironment()
173173
{
174174
// Arrange
175175
var services = new ServiceCollection();
176-
var environment = new Mock<IWebHostEnvironment>(MockBehavior.Strict);
176+
var environment = new Mock<IWebHostEnvironment>();
177177
services.AddSingleton<IWebHostEnvironment>(environment.Object);
178178

179-
environment = new Mock<IWebHostEnvironment>(MockBehavior.Strict);
179+
environment = new Mock<IWebHostEnvironment>();
180180
environment.SetupGet(e => e.ApplicationName).Returns((string)null).Verifiable();
181181
services.AddSingleton<IWebHostEnvironment>(environment.Object);
182182

@@ -196,7 +196,7 @@ public void AddMvcCore_GetsPartsForApplication()
196196
{
197197
// Arrange
198198
var services = new ServiceCollection();
199-
var environment = new Mock<IWebHostEnvironment>(MockBehavior.Strict);
199+
var environment = new Mock<IWebHostEnvironment>();
200200
var assemblyName = typeof(MvcCoreServiceCollectionExtensionsTest).Assembly.GetName();
201201
var applicationName = assemblyName.FullName;
202202
environment.SetupGet(e => e.ApplicationName).Returns(applicationName).Verifiable();

src/Mvc/samples/MvcSandbox/Components/App.razor

Lines changed: 0 additions & 13 deletions
This file was deleted.

src/Mvc/samples/MvcSandbox/Components/Pages/Index.razor

Lines changed: 0 additions & 2 deletions
This file was deleted.

src/Mvc/samples/MvcSandbox/Components/Shared/MainLayout.razor

Lines changed: 0 additions & 15 deletions
This file was deleted.

src/Mvc/samples/MvcSandbox/Components/Shared/NavMenu.razor

Lines changed: 0 additions & 29 deletions
This file was deleted.

0 commit comments

Comments
 (0)