From c7221320089f6d3c3830de53ea0e13c12f5ba426 Mon Sep 17 00:00:00 2001 From: Halil ibrahim Kalkan Date: Thu, 14 Jun 2018 01:09:21 +0300 Subject: [PATCH] Watch file changes and invalidate the bundle cache on a file change in the bundle file list. --- .../AspNetCore/Mvc/UI/Bundling/BundleCache.cs | 11 +++- .../Mvc/UI/Bundling/BundleCacheItem.cs | 18 ++++++ .../Mvc/UI/Bundling/BundleManager.cs | 59 +++++++++++++++---- .../Mvc/UI/Bundling/IBundleCache.cs | 5 +- 4 files changed, 77 insertions(+), 16 deletions(-) create mode 100644 src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleCacheItem.cs diff --git a/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleCache.cs b/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleCache.cs index 297d2cc5c5..ce6e4ac0b7 100644 --- a/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleCache.cs +++ b/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleCache.cs @@ -7,16 +7,21 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bundling { public class BundleCache : IBundleCache, ISingletonDependency { - private readonly ConcurrentDictionary> _cache; + private readonly ConcurrentDictionary _cache; public BundleCache() { - _cache = new ConcurrentDictionary>(); + _cache = new ConcurrentDictionary(); } - public List GetFiles(string bundleName, Func> factory) + public BundleCacheItem GetOrAdd(string bundleName, Func factory) { return _cache.GetOrAdd(bundleName, factory); } + + public bool Remove(string bundleName) + { + return _cache.TryRemove(bundleName, out _); + } } } \ No newline at end of file diff --git a/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleCacheItem.cs b/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleCacheItem.cs new file mode 100644 index 0000000000..71975e335c --- /dev/null +++ b/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleCacheItem.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; + +namespace Volo.Abp.AspNetCore.Mvc.UI.Bundling +{ + public class BundleCacheItem + { + public List Files { get; } + + public List WatchDisposeHandles { get; } + + public BundleCacheItem(List files) + { + Files = files; + WatchDisposeHandles = new List(); + } + } +} \ No newline at end of file diff --git a/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleManager.cs b/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleManager.cs index 3171b09056..43c3740279 100644 --- a/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleManager.cs +++ b/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleManager.cs @@ -7,6 +7,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Volo.Abp.AspNetCore.Mvc.UI.Bundling.Scripts; using Volo.Abp.AspNetCore.Mvc.UI.Bundling.Styles; +using Volo.Abp.AspNetCore.VirtualFileSystem; using Volo.Abp.DependencyInjection; using Volo.Abp.VirtualFileSystem; @@ -14,6 +15,9 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bundling { public class BundleManager : IBundleManager, ITransientDependency { + protected IHybridWebRootFileProvider WebRootFileProvider { get; } + + //TODO: Make all protected readonly to allow easily extend the bundlemanager private readonly BundlingOptions _options; private readonly IHostingEnvironment _hostingEnvironment; private readonly IScriptBundler _scriptBundler; @@ -27,25 +31,27 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bundling IScriptBundler scriptBundler, IStyleBundler styleBundler, IHostingEnvironment hostingEnvironment, - IServiceProvider serviceProvider, - IDynamicFileProvider dynamicFileProvider, - IBundleCache bundleCache) + IServiceProvider serviceProvider, + IDynamicFileProvider dynamicFileProvider, + IBundleCache bundleCache, + IHybridWebRootFileProvider webRootFileProvider) { _hostingEnvironment = hostingEnvironment; _scriptBundler = scriptBundler; _serviceProvider = serviceProvider; _dynamicFileProvider = dynamicFileProvider; _bundleCache = bundleCache; + WebRootFileProvider = webRootFileProvider; _styleBundler = styleBundler; _options = options.Value; } - public List GetStyleBundleFiles(string bundleName) + public virtual List GetStyleBundleFiles(string bundleName) { return GetBundleFiles(_options.StyleBundles, bundleName, _styleBundler); } - public List GetScriptBundleFiles(string bundleName) + public virtual List GetScriptBundleFiles(string bundleName) { return GetBundleFiles(_options.ScriptBundles, bundleName, _scriptBundler); } @@ -54,15 +60,24 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bundling { var bundleRelativePath = _options.BundleFolderName.EnsureEndsWith('/') + bundleName + "." + bundler.FileExtension; - return _bundleCache.GetFiles(bundleRelativePath, () => + var cacheItem = _bundleCache.GetOrAdd(bundleRelativePath, () => { var files = CreateFileList(bundles, bundleName); if (!IsBundlingEnabled()) { - return files; + return new BundleCacheItem(files); } + var cacheValue = new BundleCacheItem( + new List + { + "/" + bundleRelativePath + } + ); + + WatchChanges(cacheValue, files, bundleRelativePath); + var bundleResult = bundler.Bundle( new BundlerContext( bundleRelativePath, @@ -73,11 +88,33 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bundling SaveBundleResult(bundleRelativePath, bundleResult); - return new List - { - "/" + bundleRelativePath - }; + return cacheValue; }); + + return cacheItem.Files; + } + + private void WatchChanges(BundleCacheItem cacheValue, List files, string bundleRelativePath) + { + lock (cacheValue.WatchDisposeHandles) + { + foreach (var file in files) + { + var watchDisposeHandle = WebRootFileProvider.Watch(file).RegisterChangeCallback(_ => + { + lock (cacheValue.WatchDisposeHandles) + { + cacheValue.WatchDisposeHandles.ForEach(h => h.Dispose()); + cacheValue.WatchDisposeHandles.Clear(); + } + + _bundleCache.Remove(bundleRelativePath); + _dynamicFileProvider.Delete("/wwwroot/" + bundleRelativePath); //TODO: get rid of wwwroot! + }, null); + + cacheValue.WatchDisposeHandles.Add(watchDisposeHandle); + } + } } protected virtual void SaveBundleResult(string bundleRelativePath, BundleResult bundleResult) diff --git a/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/IBundleCache.cs b/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/IBundleCache.cs index bb2ac9d69c..538a7ec1a3 100644 --- a/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/IBundleCache.cs +++ b/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/IBundleCache.cs @@ -1,10 +1,11 @@ using System; -using System.Collections.Generic; namespace Volo.Abp.AspNetCore.Mvc.UI.Bundling { public interface IBundleCache { - List GetFiles(string bundleName, Func> factory); + BundleCacheItem GetOrAdd(string bundleName, Func factory); + + bool Remove(string bundleName); } } \ No newline at end of file