diff --git a/src/Smidge.Core/DefaultProfileStrategy.cs b/src/Smidge.Core/DefaultProfileStrategy.cs new file mode 100644 index 0000000..64c3c7e --- /dev/null +++ b/src/Smidge.Core/DefaultProfileStrategy.cs @@ -0,0 +1,14 @@ +using Smidge.Options; + +namespace Smidge +{ + + /// + /// An implementation of ISmidgeProfileStrategy that will always use the Default profile. + /// + /// + public class DefaultProfileStrategy : ISmidgeProfileStrategy + { + public string GetCurrentProfileName() => SmidgeOptionsProfile.Default; + } +} diff --git a/src/Smidge.Core/FileProcessors/PreProcessManager.cs b/src/Smidge.Core/FileProcessors/PreProcessManager.cs index 5d6f8fb..fca9052 100644 --- a/src/Smidge.Core/FileProcessors/PreProcessManager.cs +++ b/src/Smidge.Core/FileProcessors/PreProcessManager.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Diagnostics; using System.IO; using System.Threading; @@ -35,7 +35,7 @@ public async Task ProcessAndCacheFileAsync(IWebFile file, BundleOptions bundleOp if (file == null) throw new ArgumentNullException(nameof(file)); if (file.Pipeline == null) throw new ArgumentNullException($"{nameof(file)}.Pipeline"); - await ProcessFile(file, _bundleManager.GetAvailableOrDefaultBundleOptions(bundleOptions, false), bundleContext); + await ProcessFile(file, _bundleManager.GetAvailableOrDefaultBundleOptions(bundleOptions), bundleContext); } private async Task ProcessFile(IWebFile file, BundleOptions bundleOptions, BundleContext bundleContext) @@ -127,4 +127,4 @@ private static void FileModified(WatchedFile file) file.BundleOptions.FileWatchOptions.Changed(new FileWatchEventArgs(file)); } } -} \ No newline at end of file +} diff --git a/src/Smidge.Core/FileProcessors/PreProcessPipelineFactory.cs b/src/Smidge.Core/FileProcessors/PreProcessPipelineFactory.cs index 9698afd..4455ea3 100644 --- a/src/Smidge.Core/FileProcessors/PreProcessPipelineFactory.cs +++ b/src/Smidge.Core/FileProcessors/PreProcessPipelineFactory.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using Smidge.Models; using System.Linq; @@ -67,19 +67,20 @@ private PreProcessPipeline GetDefault(WebFileType fileType) switch (fileType) { case WebFileType.Js: + return new PreProcessPipeline(new IPreProcessor[] { - _allProcessors.Value.OfType().First(), - _allProcessors.Value.OfType().First() - }); + _allProcessors.Value.OfType().FirstOrDefault(), + _allProcessors.Value.OfType().FirstOrDefault() + }.Where(p => p != null)); case WebFileType.Css: default: return new PreProcessPipeline(new IPreProcessor[] { - _allProcessors.Value.OfType().First(), - _allProcessors.Value.OfType().First(), - _allProcessors.Value.OfType().First() - }); + _allProcessors.Value.OfType().FirstOrDefault(), + _allProcessors.Value.OfType().FirstOrDefault(), + _allProcessors.Value.OfType().FirstOrDefault() + }.Where(p => p != null)); } }); } @@ -93,4 +94,4 @@ public Func OnCreateDefault set { _setGetDefaultCallback = value; } } } -} \ No newline at end of file +} diff --git a/src/Smidge.Core/HostEnvironmentProfileStrategy.cs b/src/Smidge.Core/HostEnvironmentProfileStrategy.cs new file mode 100644 index 0000000..1fe14d5 --- /dev/null +++ b/src/Smidge.Core/HostEnvironmentProfileStrategy.cs @@ -0,0 +1,33 @@ +using Microsoft.Extensions.Hosting; +using Smidge.Options; + +namespace Smidge +{ + /// + /// An implementation of ISmidgeProfileStrategy that will use the host environment to determine if the Debug profile should be used. + /// + /// + public class HostEnvironmentProfileStrategy : ISmidgeProfileStrategy + { + private readonly IHostEnvironment _hostEnvironment; + + + public HostEnvironmentProfileStrategy(IHostEnvironment hostEnvironment) + { + _hostEnvironment = hostEnvironment; + } + + + private string _profileName; + + public string GetCurrentProfileName() => _profileName ??= GetProfileForEnvironment(_hostEnvironment); + + + protected virtual string GetProfileForEnvironment(IHostEnvironment hostEnvironment) + { + return hostEnvironment.IsDevelopment() + ? SmidgeOptionsProfile.Debug + : SmidgeOptionsProfile.Default; + } + } +} diff --git a/src/Smidge.Core/ISmidgeProfileStrategy.cs b/src/Smidge.Core/ISmidgeProfileStrategy.cs new file mode 100644 index 0000000..0c010ea --- /dev/null +++ b/src/Smidge.Core/ISmidgeProfileStrategy.cs @@ -0,0 +1,12 @@ + +namespace Smidge +{ + + /// + /// An interface that returns the name of an options profile to use for the current request. + /// + public interface ISmidgeProfileStrategy + { + string GetCurrentProfileName(); + } +} diff --git a/src/Smidge.Core/Models/Bundle.cs b/src/Smidge.Core/Models/Bundle.cs index 61d670c..a8ac5fe 100644 --- a/src/Smidge.Core/Models/Bundle.cs +++ b/src/Smidge.Core/Models/Bundle.cs @@ -45,10 +45,30 @@ public Bundle(string name, List files, BundleEnvironmentOptions bundle /// public Func, IEnumerable> OrderingCallback { get; private set; } + + + /// + /// The name of the Profile used to configure the BundleOptions for this bundle. + /// If a BundleOptions has also been specified, the ProfileName will have no effect. + /// + public string ProfileName { get; set; } + + /// + /// Set the name of the Profile to use for this bundle. + /// + /// Name of the profile. + public Bundle UseProfile(string profileName) + { + ProfileName = profileName; + return this; + } + + + /// /// Defines the options for this bundle /// - public BundleEnvironmentOptions BundleOptions { get; private set; } + public BundleEnvironmentOptions BundleOptions { get; private set; } /// /// Sets the options for the bundle diff --git a/src/Smidge.Core/Models/BundleExtensions.cs b/src/Smidge.Core/Models/BundleExtensions.cs index 7e3d4ad..cdf9efc 100644 --- a/src/Smidge.Core/Models/BundleExtensions.cs +++ b/src/Smidge.Core/Models/BundleExtensions.cs @@ -1,4 +1,5 @@ -using Smidge.Options; +using System; +using Smidge.Options; namespace Smidge.Models { @@ -11,14 +12,34 @@ public static class BundleExtensions /// /// /// + [Obsolete("Use GetBundleOptions(IBundleManager, string) and specify a configuration profile name.")] public static BundleOptions GetBundleOptions(this Bundle bundle, IBundleManager bundleMgr, bool debug) { var bundleOptions = debug - ? (bundle.BundleOptions == null ? bundleMgr.DefaultBundleOptions.DebugOptions : bundle.BundleOptions.DebugOptions) - : (bundle.BundleOptions == null ? bundleMgr.DefaultBundleOptions.ProductionOptions : bundle.BundleOptions.ProductionOptions); + ? GetBundleOptions(bundle, bundleMgr, SmidgeOptionsProfile.Debug) + : GetBundleOptions(bundle, bundleMgr, SmidgeOptionsProfile.Default); return bundleOptions; } + + /// + /// Get the bundle options from the bundle if they have been set otherwise with the defaults + /// + /// + /// + /// + /// + public static BundleOptions GetBundleOptions(this Bundle bundle, IBundleManager bundleMgr, string profileName) + { + var bundleOptions = bundle.BundleOptions == null + ? bundleMgr.DefaultBundleOptions[profileName] + : bundle.BundleOptions[profileName]; + + return bundleOptions; + } + + + /// /// Gets the default bundle options based on whether we're in debug or not @@ -26,6 +47,7 @@ public static BundleOptions GetBundleOptions(this Bundle bundle, IBundleManager /// /// /// + [Obsolete("Use GetDefaultBundleOptions(IBundleManager, string) and specify a configuration profile name.")] public static BundleOptions GetDefaultBundleOptions(this IBundleManager bundleMgr, bool debug) { var bundleOptions = debug @@ -35,11 +57,43 @@ public static BundleOptions GetDefaultBundleOptions(this IBundleManager bundleMg return bundleOptions; } + + /// + /// Gets the default bundle options for a particular configuration profile. + /// + /// + /// The name of a configuration profile. + /// + public static BundleOptions GetDefaultBundleOptions(this IBundleManager bundleMgr, string profileName) + { + var bundleOptions = bundleMgr.DefaultBundleOptions[profileName]; + + return bundleOptions; + } + + + + + + [Obsolete("Use GetAvailableOrDefaultBundleOptions(BundleOptions, string) and specify a configuration profile name.")] public static BundleOptions GetAvailableOrDefaultBundleOptions(this IBundleManager bundleMgr, BundleOptions options, bool debug) + { + return GetAvailableOrDefaultBundleOptions(bundleMgr, options, debug ? SmidgeOptionsProfile.Debug : SmidgeOptionsProfile.Default); + } + + + public static BundleOptions GetAvailableOrDefaultBundleOptions(this IBundleManager bundleMgr, BundleOptions options) + { + return GetAvailableOrDefaultBundleOptions(bundleMgr, options, SmidgeOptionsProfile.Default); + } + + public static BundleOptions GetAvailableOrDefaultBundleOptions(this IBundleManager bundleMgr, BundleOptions options, string profileName) { return options != null ? options - : bundleMgr.GetDefaultBundleOptions(debug); + : bundleMgr.GetDefaultBundleOptions(profileName); } + + } -} \ No newline at end of file +} diff --git a/src/Smidge.Core/Options/BundleEnvironmentOptions.cs b/src/Smidge.Core/Options/BundleEnvironmentOptions.cs index a184e41..b7aae59 100644 --- a/src/Smidge.Core/Options/BundleEnvironmentOptions.cs +++ b/src/Smidge.Core/Options/BundleEnvironmentOptions.cs @@ -1,12 +1,12 @@ -using System; -using Smidge.Cache; +using System.Collections.Concurrent; +using System.Collections.Generic; namespace Smidge.Options { /// - /// Defines the different bundle options for Debug vs Production + /// Defines the different bundle options for various configuration profiles such as Debug or Production /// public sealed class BundleEnvironmentOptions { @@ -20,14 +20,21 @@ public static BundleEnvironmentOptionsBuilder Create() return new BundleEnvironmentOptionsBuilder(options); } + + + private readonly IDictionary _profileOptions; + + /// /// Constructor, sets default options /// public BundleEnvironmentOptions() { + _profileOptions = new ConcurrentDictionary(); + DebugOptions = new BundleOptions { - + ProcessAsCompositeFile = false, CompressResult = false, CacheControlOptions = new CacheControlOptions @@ -36,17 +43,62 @@ public BundleEnvironmentOptions() CacheControlMaxAge = 0 } }; - ProductionOptions = new BundleOptions(); + ProductionOptions = new BundleOptions(); + } + + /// + /// The options for the "debug" profile + /// + public BundleOptions DebugOptions + { + get => this[SmidgeOptionsProfile.Debug]; + set => this[SmidgeOptionsProfile.Debug] = value; } - + /// - /// The options for debug mode + /// The options for "production" profile /// - public BundleOptions DebugOptions { get; set; } + public BundleOptions ProductionOptions + { + get => this[SmidgeOptionsProfile.Production]; + set => this[SmidgeOptionsProfile.Production] = value; + } + + /// - /// The options for production mode + /// Gets or sets the for the specified profile. + /// If the profile has not been previously configured, returns a new instance. /// - public BundleOptions ProductionOptions { get; set; } + /// Name of the profile. + /// + public BundleOptions this[string profileName] + { + get + { + if (!TryGetProfileOptions(profileName, out BundleOptions options)) + { + // Initialise a new BundleOptions for the requested profile + options = new BundleOptions(); + _profileOptions.Add(profileName, options); + } + + return options; + } + set => _profileOptions[profileName] = value; + } + + + /// + /// Gets the for the specified profile, if the profile has previously been configured. + /// + /// Name of the profile. + /// The profile options. + /// + public bool TryGetProfileOptions(string profileName, out BundleOptions options) + { + return _profileOptions.TryGetValue(profileName, out options); + } + } -} \ No newline at end of file +} diff --git a/src/Smidge.Core/Options/BundleEnvironmentOptionsBuilder.cs b/src/Smidge.Core/Options/BundleEnvironmentOptionsBuilder.cs index 9fa4a32..1f3e27f 100644 --- a/src/Smidge.Core/Options/BundleEnvironmentOptionsBuilder.cs +++ b/src/Smidge.Core/Options/BundleEnvironmentOptionsBuilder.cs @@ -1,4 +1,5 @@ -using System; +using System; +using System.Collections.Generic; namespace Smidge.Options { @@ -8,29 +9,39 @@ namespace Smidge.Options public sealed class BundleEnvironmentOptionsBuilder { private readonly BundleEnvironmentOptions _bundleEnvironmentOptions; - private Action _debugBuilder; - private Action _productionBuilder; - private bool _built = false; + + private readonly Dictionary> _profileBuilders; + private readonly bool _built = false; public BundleEnvironmentOptionsBuilder(BundleEnvironmentOptions bundleEnvironmentOptions) { + _profileBuilders = new Dictionary>(); + _bundleEnvironmentOptions = bundleEnvironmentOptions; } public BundleEnvironmentOptionsBuilder ForDebug(Action debugBuilder) { - if (debugBuilder == null) throw new ArgumentNullException(nameof(debugBuilder)); - _debugBuilder = debugBuilder; - return this; + return ForProfile(SmidgeOptionsProfile.Debug, debugBuilder); } public BundleEnvironmentOptionsBuilder ForProduction(Action productionBuilder) { - if (productionBuilder == null) throw new ArgumentNullException(nameof(productionBuilder)); - _productionBuilder = productionBuilder; - return this; + return ForProfile(SmidgeOptionsProfile.Production, productionBuilder); } + public BundleEnvironmentOptionsBuilder ForProfile(string profileName, Action profileOptionsBuilder) + { + if (string.IsNullOrEmpty(profileName)) + throw new ArgumentNullException(nameof(profileName)); + + if (profileOptionsBuilder == null) + throw new ArgumentNullException(nameof(profileOptionsBuilder)); + + _profileBuilders.Add(profileName, profileOptionsBuilder); + return this; + } + /// /// Builds the bundle environment options based on the callbacks specified /// @@ -39,16 +50,13 @@ public BundleEnvironmentOptions Build() { if (!_built) { - if (_debugBuilder != null) - { - _debugBuilder(new BundleOptionsBuilder(_bundleEnvironmentOptions.DebugOptions)); - } - if (_productionBuilder != null) + foreach (var (profileName, profileBuilder) in _profileBuilders) { - _productionBuilder(new BundleOptionsBuilder(_bundleEnvironmentOptions.ProductionOptions)); + BundleOptions options = _bundleEnvironmentOptions[profileName]; + profileBuilder.Invoke(new BundleOptionsBuilder(options)); } } return _bundleEnvironmentOptions; } } -} \ No newline at end of file +} diff --git a/src/Smidge.Core/Options/SmidgeOptionsProfile.cs b/src/Smidge.Core/Options/SmidgeOptionsProfile.cs new file mode 100644 index 0000000..d1c2416 --- /dev/null +++ b/src/Smidge.Core/Options/SmidgeOptionsProfile.cs @@ -0,0 +1,11 @@ + +namespace Smidge.Options +{ + public static class SmidgeOptionsProfile + { + public const string Default = Production; + + public const string Debug = "Debug"; + public const string Production = "Production"; + } +} diff --git a/src/Smidge.Web/Views/Home/Index.cshtml b/src/Smidge.Web/Views/Home/Index.cshtml index 393d56b..4def666 100644 --- a/src/Smidge.Web/Views/Home/Index.cshtml +++ b/src/Smidge.Web/Views/Home/Index.cshtml @@ -38,12 +38,12 @@ @await Html.PartialAsync("TopBar") - @await Html.PartialAsync("LoadedDependencies", false) + @await Html.PartialAsync("LoadedDependencies", (bool?)null) - @await SmidgeHelper.JsHereAsync(debug: false) + @await SmidgeHelper.JsHereAsync() @await SmidgeHelper.JsHereAsync("test-bundle-1") @await SmidgeHelper.JsHereAsync("test-bundle-2") - @await SmidgeHelper.JsHereAsync("no-files", debug: false) + @await SmidgeHelper.JsHereAsync("no-files") @await SmidgeHelper.JsHereAsync("test-bundle-10") diff --git a/src/Smidge.Web/Views/Home/SubFolder.cshtml b/src/Smidge.Web/Views/Home/SubFolder.cshtml index fef9db5..97ef221 100644 --- a/src/Smidge.Web/Views/Home/SubFolder.cshtml +++ b/src/Smidge.Web/Views/Home/SubFolder.cshtml @@ -32,9 +32,9 @@ @await Html.PartialAsync("TopBar") - @await Html.PartialAsync("LoadedDependencies", false) + @await Html.PartialAsync("LoadedDependencies", (bool?)null) - @await SmidgeHelper.JsHereAsync(debug: false) + @await SmidgeHelper.JsHereAsync() @await SmidgeHelper.JsHereAsync("test-bundle-1") @await SmidgeHelper.JsHereAsync("test-bundle-2") diff --git a/src/Smidge.Web/Views/Shared/LoadedDependencies.cshtml b/src/Smidge.Web/Views/Shared/LoadedDependencies.cshtml index 21f36c1..77241e3 100644 --- a/src/Smidge.Web/Views/Shared/LoadedDependencies.cshtml +++ b/src/Smidge.Web/Views/Shared/LoadedDependencies.cshtml @@ -1,5 +1,7 @@ -@model bool +@model bool? + @using Smidge.Models; + @inject Smidge.SmidgeHelper SmidgeHelper @inject Smidge.IBundleManager BundleManager @@ -43,4 +45,4 @@
JS loading debug output: -
\ No newline at end of file + diff --git a/src/Smidge/Controllers/AddCompressionHeaderAttribute.cs b/src/Smidge/Controllers/AddCompressionHeaderAttribute.cs index f2ca3fb..0c472d8 100644 --- a/src/Smidge/Controllers/AddCompressionHeaderAttribute.cs +++ b/src/Smidge/Controllers/AddCompressionHeaderAttribute.cs @@ -1,8 +1,9 @@ -using System; +using System; using System.Linq; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.Extensions.DependencyInjection; using Smidge.Models; +using Smidge.Options; namespace Smidge.Controllers { @@ -17,6 +18,7 @@ public sealed class AddCompressionHeaderAttribute : Attribute, IFilterFactory, I public IFilterMetadata CreateInstance(IServiceProvider serviceProvider) { return new AddCompressionFilter( + serviceProvider.GetRequiredService(), serviceProvider.GetRequiredService(), serviceProvider.GetRequiredService()); } @@ -27,11 +29,13 @@ public IFilterMetadata CreateInstance(IServiceProvider serviceProvider) private class AddCompressionFilter : IActionFilter { + private readonly ISmidgeProfileStrategy _profileStrategy; private readonly IRequestHelper _requestHelper; private readonly IBundleManager _bundleManager; - public AddCompressionFilter(IRequestHelper requestHelper, IBundleManager bundleManager) + public AddCompressionFilter(ISmidgeProfileStrategy profileStrategy, IRequestHelper requestHelper, IBundleManager bundleManager) { + _profileStrategy = profileStrategy ?? throw new ArgumentNullException(nameof(profileStrategy)); _requestHelper = requestHelper ?? throw new ArgumentNullException(nameof(requestHelper)); _bundleManager = bundleManager ?? throw new ArgumentNullException(nameof(bundleManager)); } @@ -62,7 +66,22 @@ public void OnActionExecuted(ActionExecutedContext context) //check if it's a bundle (not composite file) if (file is BundleRequestModel bundleRequest && _bundleManager.TryGetValue(bundleRequest.FileKey, out var bundle)) { - var bundleOptions = bundle.GetBundleOptions(_bundleManager, bundleRequest.Debug); + string profileName; + + // For backwards compatibility we'll use the Debug profile if it was explicitly requested in the request. + if (bundleRequest.Debug) + { + profileName = SmidgeOptionsProfile.Debug; + } + else + { + // If the Bundle explicitly specifies a profile to use then use it otherwise use the current profile + profileName = !string.IsNullOrEmpty(bundle.ProfileName) + ? bundle.ProfileName + : _profileStrategy.GetCurrentProfileName(); + } + + var bundleOptions = bundle.GetBundleOptions(_bundleManager, profileName); enableCompression = bundleOptions.CompressResult; } @@ -72,4 +91,4 @@ public void OnActionExecuted(ActionExecutedContext context) } } } -} \ No newline at end of file +} diff --git a/src/Smidge/Controllers/AddExpiryHeadersAttribute.cs b/src/Smidge/Controllers/AddExpiryHeadersAttribute.cs index 2929d58..b1db4c5 100644 --- a/src/Smidge/Controllers/AddExpiryHeadersAttribute.cs +++ b/src/Smidge/Controllers/AddExpiryHeadersAttribute.cs @@ -13,7 +13,10 @@ namespace Smidge.Controllers ///
public sealed class AddExpiryHeadersAttribute : Attribute, IFilterFactory, IOrderedFilter { - public IFilterMetadata CreateInstance(IServiceProvider serviceProvider) => new AddExpiryHeaderFilter(serviceProvider.GetRequiredService(), serviceProvider.GetRequiredService()); + public IFilterMetadata CreateInstance(IServiceProvider serviceProvider) => new AddExpiryHeaderFilter( + serviceProvider.GetRequiredService(), + serviceProvider.GetRequiredService(), + serviceProvider.GetRequiredService()); public bool IsReusable => true; @@ -21,13 +24,15 @@ public sealed class AddExpiryHeadersAttribute : Attribute, IFilterFactory, IOrde public sealed class AddExpiryHeaderFilter : IActionFilter { + private readonly ISmidgeProfileStrategy _profileStrategy; private readonly IHasher _hasher; private readonly IBundleManager _bundleManager; - public AddExpiryHeaderFilter(IHasher hasher, IBundleManager bundleManager) + public AddExpiryHeaderFilter(ISmidgeProfileStrategy profileStrategy, IHasher hasher, IBundleManager bundleManager) { - _hasher = hasher; - _bundleManager = bundleManager; + _profileStrategy = profileStrategy ?? throw new ArgumentNullException(nameof(profileStrategy)); + _hasher = hasher ?? throw new ArgumentNullException(nameof(hasher)); + _bundleManager = bundleManager ?? throw new ArgumentNullException(nameof(bundleManager)); } public void OnActionExecuting(ActionExecutingContext context) @@ -56,15 +61,34 @@ public void OnActionExecuted(ActionExecutedContext context) var enableETag = true; var cacheControlMaxAge = 10 * 24; //10 days + string profileName; BundleOptions bundleOptions; - - if (_bundleManager.TryGetValue(file.FileKey, out Bundle b)) + + if (_bundleManager.TryGetValue(file.FileKey, out Bundle bundle)) { - bundleOptions = b.GetBundleOptions(_bundleManager, file.Debug); + // For backwards compatibility we'll use the Debug profile if it was explicitly requested in the request. + if (file.Debug) + { + profileName = SmidgeOptionsProfile.Debug; + } + else + { + // If the Bundle explicitly specifies a profile to use then use it otherwise use the current profile + profileName = !string.IsNullOrEmpty(bundle.ProfileName) + ? bundle.ProfileName + : _profileStrategy.GetCurrentProfileName(); + } + + bundleOptions = bundle.GetBundleOptions(_bundleManager, profileName); } else { - bundleOptions = file.Debug ? _bundleManager.DefaultBundleOptions.DebugOptions : _bundleManager.DefaultBundleOptions.ProductionOptions; + // For backwards compatibility we'll use the Debug profile if it was explicitly requested in the request. + profileName = file.Debug + ? SmidgeOptionsProfile.Debug + : _profileStrategy.GetCurrentProfileName(); + + _bundleManager.DefaultBundleOptions.TryGetProfileOptions(profileName, out bundleOptions); } if (bundleOptions != null) diff --git a/src/Smidge/Controllers/SmidgeController.cs b/src/Smidge/Controllers/SmidgeController.cs index 3df51b0..01116df 100644 --- a/src/Smidge/Controllers/SmidgeController.cs +++ b/src/Smidge/Controllers/SmidgeController.cs @@ -13,6 +13,8 @@ using Smidge.FileProcessors; using Smidge.Cache; using Microsoft.AspNetCore.Authorization; +using System.Reflection.Metadata; +using Smidge.Options; namespace Smidge.Controllers { @@ -27,6 +29,7 @@ namespace Smidge.Controllers [AllowAnonymous] public class SmidgeController : Controller { + private readonly ISmidgeProfileStrategy _profileStrategy; private readonly ISmidgeFileSystem _fileSystem; private readonly IBundleManager _bundleManager; private readonly IBundleFileSetGenerator _fileSetGenerator; @@ -44,6 +47,7 @@ public class SmidgeController : Controller /// /// public SmidgeController( + ISmidgeProfileStrategy profileStrategy, ISmidgeFileSystem fileSystemHelper, IBundleManager bundleManager, IBundleFileSetGenerator fileSetGenerator, @@ -51,6 +55,7 @@ public SmidgeController( IPreProcessManager preProcessManager, ILogger logger) { + _profileStrategy = profileStrategy ?? throw new ArgumentNullException(nameof(profileStrategy)); _fileSystem = fileSystemHelper ?? throw new ArgumentNullException(nameof(fileSystemHelper)); _bundleManager = bundleManager ?? throw new ArgumentNullException(nameof(bundleManager)); _fileSetGenerator = fileSetGenerator ?? throw new ArgumentNullException(nameof(fileSetGenerator)); @@ -72,8 +77,24 @@ public async Task Bundle( return NotFound(); } - var bundleOptions = foundBundle.GetBundleOptions(_bundleManager, bundleModel.Debug); + string profileName; + // For backwards compatibility we'll use the Debug profile if it was explicitly requested in the request. + if (bundleModel.Debug) + { + profileName = SmidgeOptionsProfile.Debug; + } + else + { + // If the Bundle explicitly specifies a profile to use then use it otherwise use the current profile + profileName = !string.IsNullOrEmpty(foundBundle.ProfileName) + ? foundBundle.ProfileName + : _profileStrategy.GetCurrentProfileName(); + } + + //get the bundle options from the bundle if they have been set otherwise with the defaults + var bundleOptions = foundBundle.GetBundleOptions(_bundleManager, profileName); + var cacheBusterValue = bundleModel.ParsedPath.CacheBusterValue; //now we need to determine if this bundle has already been created @@ -241,4 +262,4 @@ private async Task GetCombinedStreamAsync(IEnumerable files, } } } -} +} \ No newline at end of file diff --git a/src/Smidge/SmidgeHelper.cs b/src/Smidge/SmidgeHelper.cs index 3cb54a8..73d818c 100644 --- a/src/Smidge/SmidgeHelper.cs +++ b/src/Smidge/SmidgeHelper.cs @@ -1,4 +1,4 @@ -using Smidge.Models; +using Smidge.Models; using System; using System.Collections.Generic; using System.Linq; @@ -10,6 +10,7 @@ using Smidge.CompositeFiles; using Smidge.FileProcessors; using Smidge.Hashing; +using Smidge.Options; namespace Smidge { @@ -18,6 +19,7 @@ namespace Smidge ///
public class SmidgeHelper : ISmidgeRequire { + private readonly ISmidgeProfileStrategy _profileStrategy; private readonly DynamicallyRegisteredWebFiles _dynamicallyRegisteredWebFiles; private readonly IPreProcessManager _preProcessManager; private readonly ISmidgeFileSystem _fileSystem; @@ -34,6 +36,7 @@ public class SmidgeHelper : ISmidgeRequire /// /// Constructor /// + /// /// /// /// @@ -46,6 +49,7 @@ public class SmidgeHelper : ISmidgeRequire /// /// public SmidgeHelper( + ISmidgeProfileStrategy profileStrategy, IBundleFileSetGenerator fileSetGenerator, DynamicallyRegisteredWebFiles dynamicallyRegisteredWebFiles, IPreProcessManager preProcessManager, @@ -58,6 +62,7 @@ public SmidgeHelper( IHttpContextAccessor httpContextAccessor, CacheBusterResolver cacheBusterResolver) { + _profileStrategy = profileStrategy ?? throw new ArgumentNullException(nameof(profileStrategy)); _fileSetGenerator = fileSetGenerator ?? throw new ArgumentNullException(nameof(fileSetGenerator)); _processorFactory = processorFactory ?? throw new ArgumentNullException(nameof(processorFactory)); _urlManager = urlManager ?? throw new ArgumentNullException(nameof(urlManager)); @@ -71,7 +76,7 @@ public SmidgeHelper( _fileBatcher = new FileBatcher(_fileSystem, _requestHelper, hasher); } - public async Task JsHereAsync(string bundleName, bool debug = false) + public async Task JsHereAsync(string bundleName, bool? debug = null) { var urls = await GenerateJsUrlsAsync(bundleName, debug); var result = new StringBuilder(); @@ -83,7 +88,7 @@ public async Task JsHereAsync(string bundleName, bool debug = false) return new HtmlString(result.ToString()); } - public async Task CssHereAsync(string bundleName, bool debug = false) + public async Task CssHereAsync(string bundleName, bool? debug = null) { var urls = await GenerateCssUrlsAsync(bundleName, debug); var result = new StringBuilder(); @@ -103,7 +108,7 @@ public async Task CssHereAsync(string bundleName, bool debug = false /// TODO: Once the tags are rendered the collection on the context is cleared. Therefore if this method is called multiple times it will /// render anything that has been registered as 'pending' but has not been rendered. /// - public async Task JsHereAsync(PreProcessPipeline pipeline = null, bool debug = false) + public async Task JsHereAsync(PreProcessPipeline pipeline = null, bool? debug = null) { var result = new StringBuilder(); var urls = await GenerateJsUrlsAsync(pipeline, debug); @@ -122,7 +127,7 @@ public async Task JsHereAsync(PreProcessPipeline pipeline = null, bo /// TODO: Once the tags are rendered the collection on the context is cleared. Therefore if this method is called multiple times it will /// render anything that has been registered as 'pending' but has not been rendered. /// - public async Task CssHereAsync(PreProcessPipeline pipeline = null, bool debug = false) + public async Task CssHereAsync(PreProcessPipeline pipeline = null, bool? debug = null) { var result = new StringBuilder(); var urls = await GenerateCssUrlsAsync(pipeline, debug); @@ -137,12 +142,12 @@ public async Task CssHereAsync(PreProcessPipeline pipeline = null, b /// Generates the list of URLs to render based on what is dynamically registered /// /// - public async Task> GenerateJsUrlsAsync(PreProcessPipeline pipeline = null, bool debug = false) + public async Task> GenerateJsUrlsAsync(PreProcessPipeline pipeline = null, bool? debug = null) { return await GenerateUrlsAsync(_dynamicallyRegisteredWebFiles.JavaScriptFiles, WebFileType.Js, pipeline, debug); } - public Task> GenerateJsUrlsAsync(string bundleName, bool debug = false) + public Task> GenerateJsUrlsAsync(string bundleName, bool? debug = null) { return Task.FromResult(GenerateBundleUrlsAsync(bundleName, ".js", debug)); } @@ -151,12 +156,12 @@ public Task> GenerateJsUrlsAsync(string bundleName, bool deb /// Generates the list of URLs to render based on what is dynamically registered /// /// - public async Task> GenerateCssUrlsAsync(PreProcessPipeline pipeline = null, bool debug = false) + public async Task> GenerateCssUrlsAsync(PreProcessPipeline pipeline = null, bool? debug = null) { return await GenerateUrlsAsync(_dynamicallyRegisteredWebFiles.CssFiles, WebFileType.Css, pipeline, debug); } - public Task> GenerateCssUrlsAsync(string bundleName, bool debug = false) + public Task> GenerateCssUrlsAsync(string bundleName, bool? debug = null) { return Task.FromResult(GenerateBundleUrlsAsync(bundleName, ".css", debug)); } @@ -168,7 +173,7 @@ public Task> GenerateCssUrlsAsync(string bundleName, bool de /// /// /// - private IEnumerable GenerateBundleUrlsAsync(string bundleName, string fileExt, bool debug) + private IEnumerable GenerateBundleUrlsAsync(string bundleName, string fileExt, bool? debug = null) { //TODO: We should cache this, but problem is how do we do that with file watchers enabled? We'd still have to lookup the bundleOptions // or maybe we just cache when file watchers are not enabled - probably the way to do it @@ -184,8 +189,23 @@ private IEnumerable GenerateBundleUrlsAsync(string bundleName, string fi var result = new List(); + string profileName; + if (debug != null) + { + // Backwards compatibility - use the Debug parameter to choose the profile to use + profileName = debug.Value ? SmidgeOptionsProfile.Debug : SmidgeOptionsProfile.Default; + } + else + { + // If the Bundle explicitly specifies a profile to use then use it otherwise use the current profile + profileName = !string.IsNullOrEmpty(bundle.ProfileName) + ? bundle.ProfileName + : _profileStrategy.GetCurrentProfileName(); + } + + //get the bundle options from the bundle if they have been set otherwise with the defaults - var bundleOptions = bundle.GetBundleOptions(_bundleManager, debug); + var bundleOptions = bundle.GetBundleOptions(_bundleManager, profileName); var cacheBuster = _cacheBusterResolver.GetCacheBuster(bundleOptions.GetCacheBusterType()); var cacheBusterValue = cacheBuster.GetValue(); @@ -197,11 +217,14 @@ private IEnumerable GenerateBundleUrlsAsync(string bundleName, string fi _processorFactory.CreateDefault( //the file type in the bundle will always be the same bundle.Files[0].DependencyType)); - result.AddRange(files.Select(d => _urlManager.AppendCacheBuster(_requestHelper.Content(d), debug, cacheBusterValue))); + + // For backwards compatibility we'll only generate a debug token in the url if Debug was explicitly requested. + result.AddRange(files.Select(d => _urlManager.AppendCacheBuster(_requestHelper.Content(d), debug is true, cacheBusterValue))); return result; } - var url = _urlManager.GetUrl(bundleName, fileExt, debug, cacheBusterValue); + // For backwards compatibility we'll only generate a debug token in the url if Debug was explicitly requested. + var url = _urlManager.GetUrl(bundleName, fileExt, debug is true, cacheBusterValue); if (!string.IsNullOrWhiteSpace(url)) { result.Add(url); @@ -218,22 +241,31 @@ private IEnumerable GenerateBundleUrlsAsync(string bundleName, string fi /// /// /// - private async Task> GenerateUrlsAsync( - IEnumerable files, - WebFileType fileType, - PreProcessPipeline pipeline = null, - bool debug = false) + private async Task> GenerateUrlsAsync(IEnumerable files, WebFileType fileType, PreProcessPipeline pipeline = null, bool? debug = null) { var result = new List(); var orderedFiles = _fileSetGenerator.GetOrderedFileSet(files, pipeline ?? _processorFactory.CreateDefault(fileType)); - var cacheBuster = _cacheBusterResolver.GetCacheBuster(_bundleManager.GetDefaultBundleOptions(debug).GetCacheBusterType()); + string profileName; + if (debug != null) + { + // Backwards compatibility - use the Debug parameter to choose the profile to use + profileName = debug.Value ? SmidgeOptionsProfile.Debug : SmidgeOptionsProfile.Default; + } + else + { + profileName = _profileStrategy.GetCurrentProfileName(); + } + + var bundleOptions = _bundleManager.GetDefaultBundleOptions(profileName); + + var cacheBuster = _cacheBusterResolver.GetCacheBuster(bundleOptions.GetCacheBusterType()); var cacheBusterValue = cacheBuster.GetValue(); - if (debug) + if (!bundleOptions.ProcessAsCompositeFile) { - return orderedFiles.Select(x => _urlManager.AppendCacheBuster(_requestHelper.Content(x), debug, cacheBusterValue)); + return orderedFiles.Select(x => _urlManager.AppendCacheBuster(_requestHelper.Content(x), true, cacheBusterValue)); } var compression = _requestHelper.GetClientCompression(_httpContextAccessor.HttpContext.Request.Headers); @@ -262,7 +294,7 @@ private async Task> GenerateUrlsAsync( { //now we need to determine if these files have already been minified - var defaultBundleOptions = _bundleManager.GetDefaultBundleOptions(false); + //var defaultBundleOptions = _bundleManager.GetDefaultBundleOptions(false); var cacheFile = _fileSystem.CacheFileSystem.GetCachedCompositeFile(cacheBusterValue, compression, u.Key, out _); if (!cacheFile.Exists) @@ -272,7 +304,7 @@ private async Task> GenerateUrlsAsync( //need to process/minify these files - need to use their original paths of course foreach (var file in batch.Select(x => x.Original)) { - await _preProcessManager.ProcessAndCacheFileAsync(file, null, bundleContext); + await _preProcessManager.ProcessAndCacheFileAsync(file, bundleOptions, bundleContext); } } } diff --git a/src/Smidge/SmidgeStartup.cs b/src/Smidge/SmidgeStartup.cs index 81249e9..3ba8ca8 100644 --- a/src/Smidge/SmidgeStartup.cs +++ b/src/Smidge/SmidgeStartup.cs @@ -1,4 +1,4 @@ -using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Infrastructure; @@ -34,6 +34,7 @@ public static IServiceCollection AddSmidge(this IServiceCollection services, ICo services.TryAddSingleton(); services.TryAddSingleton(); + services.AddSingleton(); services.AddTransient, SmidgeOptionsSetup>(); services.AddSingleton(); diff --git a/src/Smidge/TagHelpers/SmidgeLinkTagHelper.cs b/src/Smidge/TagHelpers/SmidgeLinkTagHelper.cs index fdae1ed..0bee3f2 100644 --- a/src/Smidge/TagHelpers/SmidgeLinkTagHelper.cs +++ b/src/Smidge/TagHelpers/SmidgeLinkTagHelper.cs @@ -39,9 +39,12 @@ public SmidgeLinkTagHelper(SmidgeHelper smidgeHelper, IBundleManager bundleManag _encoder = encoder; } + /// + /// Gets or sets a value indicating whether to generate content based on the debug or production configuration profile. + /// If left unset then the configured will determine if the debug profile is used. + /// [HtmlAttributeName("debug")] - public bool Debug { get; set; } - + public bool? Debug { get; set; } /// /// TODO: Need to figure out why we need this. If the order is default and executes 'after' the /// default tag helpers like the script tag helper and url resolution tag helper, the url resolution diff --git a/src/Smidge/TagHelpers/SmidgeScriptTagHelper.cs b/src/Smidge/TagHelpers/SmidgeScriptTagHelper.cs index 75ed561..efb6beb 100644 --- a/src/Smidge/TagHelpers/SmidgeScriptTagHelper.cs +++ b/src/Smidge/TagHelpers/SmidgeScriptTagHelper.cs @@ -59,8 +59,12 @@ public SmidgeScriptTagHelper(SmidgeHelper smidgeHelper, IBundleManager bundleMan [HtmlAttributeName("src")] public string Source { get; set; } + /// + /// Gets or sets a value indicating whether to generate content based on the debug or production configuration profile. + /// If left unset then the configured will determine if the debug profile is used. + /// [HtmlAttributeName("debug")] - public bool Debug { get; set; } + public bool? Debug { get; set; } public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) { diff --git a/test/Smidge.Tests/Helpers/FakeCacheBuster.cs b/test/Smidge.Tests/Helpers/FakeCacheBuster.cs new file mode 100644 index 0000000..02aee3f --- /dev/null +++ b/test/Smidge.Tests/Helpers/FakeCacheBuster.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; +using Smidge.Cache; + +namespace Smidge.Tests.Helpers +{ + public class FakeCacheBuster : ICacheBuster + { + + public static readonly IEnumerable Instances = new[] { new FakeCacheBuster() }; + + + public string GetValue() => "00000"; + } +} diff --git a/test/Smidge.Tests/Helpers/FakeProfileStrategy.cs b/test/Smidge.Tests/Helpers/FakeProfileStrategy.cs new file mode 100644 index 0000000..36fb9c3 --- /dev/null +++ b/test/Smidge.Tests/Helpers/FakeProfileStrategy.cs @@ -0,0 +1,29 @@ +using Smidge.Options; + +namespace Smidge.Tests.Helpers +{ + public class FakeProfileStrategy : ISmidgeProfileStrategy + { + public static readonly ISmidgeProfileStrategy DebugProfileStrategy = new FakeProfileStrategy(SmidgeOptionsProfile.Debug); + public static readonly ISmidgeProfileStrategy DefaultProfileStrategy = new FakeProfileStrategy(SmidgeOptionsProfile.Default); + + + public FakeProfileStrategy() + { + ProfileName = SmidgeOptionsProfile.Default; + } + + public FakeProfileStrategy(string profileName) + { + ProfileName = profileName; + } + + + public string ProfileName { get; set; } + + + public string GetCurrentProfileName() => ProfileName; + + + } +} diff --git a/test/Smidge.Tests/Helpers/FakeWebsiteInfo.cs b/test/Smidge.Tests/Helpers/FakeWebsiteInfo.cs new file mode 100644 index 0000000..22052f7 --- /dev/null +++ b/test/Smidge.Tests/Helpers/FakeWebsiteInfo.cs @@ -0,0 +1,13 @@ +using System; + +namespace Smidge.Tests.Helpers +{ + public class FakeWebsiteInfo : IWebsiteInfo + { + + private Uri _baseUrl; + public Uri GetBaseUrl() => _baseUrl ??= new Uri("http://test.com"); + + public string GetBasePath() => string.Empty; + } +} diff --git a/test/Smidge.Tests/SmidgeHelperTests.cs b/test/Smidge.Tests/SmidgeHelperTests.cs index 71ff88d..a0d67bb 100644 --- a/test/Smidge.Tests/SmidgeHelperTests.cs +++ b/test/Smidge.Tests/SmidgeHelperTests.cs @@ -1,83 +1,97 @@ using System; -using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Moq; using Smidge.CompositeFiles; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Dazinator.Extensions.FileProviders; +using Dazinator.Extensions.FileProviders.InMemory; +using Dazinator.Extensions.FileProviders.InMemory.Directory; using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; -using Smidge; using Smidge.Cache; using Smidge.Hashing; using Smidge.FileProcessors; using Smidge.Options; +using Smidge.Tests.Helpers; using Xunit; namespace Smidge.Tests { public class SmidgeHelperTests { - private readonly IUrlManager _urlManager = Mock.Of(); private readonly IFileProvider _fileProvider = Mock.Of(); private readonly ICacheFileSystem _cacheProvider = Mock.Of(); - private readonly IFileProviderFilter _fileProviderFilter = Mock.Of(); private readonly IHasher _hasher = Mock.Of(); private readonly IEnumerable _preProcessors = new List(); private readonly IBundleFileSetGenerator _fileSetGenerator; private readonly DynamicallyRegisteredWebFiles _dynamicallyRegisteredWebFiles; private readonly SmidgeFileSystem _fileSystemHelper; private readonly PreProcessManager _preProcessManager; - private Mock> _smidgeOptions; private readonly PreProcessPipelineFactory _processorFactory; private readonly IBundleManager _bundleManager; private readonly IRequestHelper _requestHelper; - private readonly Mock _httpContextAccessor; - private Mock _httpContext; + private readonly IUrlManager _urlManager; + private readonly CacheBusterResolver _cacheBusterResolver; + private readonly IHttpContextAccessor _httpContextAccessor; public SmidgeHelperTests() { - // var config = Mock.Of(); - _httpContext = new Mock(); - _httpContextAccessor = new Mock(); - _httpContextAccessor.Setup(x => x.HttpContext).Returns(_httpContext.Object); - + var httpContextAccessor = new Mock(); + httpContextAccessor.Setup(x => x.HttpContext).Returns(Mock.Of); + _httpContextAccessor = httpContextAccessor.Object; + _dynamicallyRegisteredWebFiles = new DynamicallyRegisteredWebFiles(); - _fileSystemHelper = new SmidgeFileSystem(_fileProvider, _fileProviderFilter, _cacheProvider, Mock.Of()); - _smidgeOptions = new Mock>(); - _smidgeOptions.Setup(opt => opt.Value).Returns(new SmidgeOptions + _fileSystemHelper = new SmidgeFileSystem(_fileProvider, new DefaultFileProviderFilter(), _cacheProvider, new FakeWebsiteInfo()); + + var smidgeConfig = new Mock(); + smidgeConfig.Setup(c => c.KeepFileExtensions).Returns(false); + + var smidgeOptions = new Mock>(); + smidgeOptions.Setup(opt => opt.Value).Returns(() => { - DefaultBundleOptions = new BundleEnvironmentOptions() + var options = new SmidgeOptions + { + UrlOptions = new UrlManagerOptions(), + DefaultBundleOptions = new BundleEnvironmentOptions() + }; + options.DefaultBundleOptions.DebugOptions.SetCacheBusterType(); + options.DefaultBundleOptions.ProductionOptions.SetCacheBusterType(); + return options; }); - _requestHelper = Mock.Of(); + _requestHelper = new RequestHelper(new FakeWebsiteInfo()); + _urlManager = new DefaultUrlManager(smidgeOptions.Object, _hasher, _requestHelper, smidgeConfig.Object); + + _cacheBusterResolver = new CacheBusterResolver(FakeCacheBuster.Instances); + _processorFactory = new PreProcessPipelineFactory(new Lazy>(() => _preProcessors)); - _bundleManager = new BundleManager(_smidgeOptions.Object, Mock.Of>()); + _bundleManager = new BundleManager(smidgeOptions.Object, Mock.Of>()); _preProcessManager = new PreProcessManager( - _fileSystemHelper, - _bundleManager, + _fileSystemHelper, + _bundleManager, Mock.Of>()); _fileSetGenerator = new BundleFileSetGenerator( - _fileSystemHelper, - new FileProcessingConventions(_smidgeOptions.Object, new List())); + _fileSystemHelper, + new FileProcessingConventions(smidgeOptions.Object, new List())); } [Fact] public async Task JsHereAsync_Returns_Empty_String_Result_When_No_Files_Found() { var sut = new SmidgeHelper( + FakeProfileStrategy.DefaultProfileStrategy, _fileSetGenerator, - _dynamicallyRegisteredWebFiles, _preProcessManager, _fileSystemHelper, + _dynamicallyRegisteredWebFiles, _preProcessManager, _fileSystemHelper, _hasher, _bundleManager, _processorFactory, _urlManager, _requestHelper, - _httpContextAccessor.Object, - new CacheBusterResolver(Enumerable.Empty())); + _httpContextAccessor, _cacheBusterResolver); _bundleManager.CreateJs("empty", Array.Empty()); - var result = (await sut.JsHereAsync("empty", false)).ToString(); + var result = (await sut.JsHereAsync("empty")).ToString(); Assert.Equal(string.Empty, result); } @@ -85,50 +99,243 @@ public async Task JsHereAsync_Returns_Empty_String_Result_When_No_Files_Found() public async Task Generate_Css_Urls_For_Non_Existent_Bundle_Throws_Exception() { var sut = new SmidgeHelper( + FakeProfileStrategy.DebugProfileStrategy, _fileSetGenerator, - _dynamicallyRegisteredWebFiles, _preProcessManager, _fileSystemHelper, - _hasher, _bundleManager, _processorFactory, _urlManager, _requestHelper, - _httpContextAccessor.Object, - new CacheBusterResolver(Enumerable.Empty())); + _dynamicallyRegisteredWebFiles, _preProcessManager, _fileSystemHelper, + _hasher, _bundleManager, _processorFactory, _urlManager, _requestHelper, + _httpContextAccessor, _cacheBusterResolver); var exception = await Assert.ThrowsAsync ( - async () => await sut.GenerateCssUrlsAsync("DoesntExist", true) + async () => await sut.GenerateCssUrlsAsync("DoesntExist") ); } + + [Fact] + public async Task Generate_Css_Urls_Returns_SingleBundleUrl_When_Default_Profile_Is_Used() + { + var sut = new SmidgeHelper( + FakeProfileStrategy.DefaultProfileStrategy, + _fileSetGenerator, + _dynamicallyRegisteredWebFiles, _preProcessManager, _fileSystemHelper, + _hasher, _bundleManager, _processorFactory, _urlManager, _requestHelper, + _httpContextAccessor, _cacheBusterResolver); + + _bundleManager.CreateCss("test", new[] + { + "file1.css", + "file2.css" + }); + + var dir = new InMemoryDirectory(); + dir.AddFile("", new StringFileInfo("File1", "file1.css")); + dir.AddFile("", new StringFileInfo("File2", "file2.css")); + var fileProvider = new InMemoryFileProvider(dir); + + // Configure the mock file provider to use the temporary file provider we've just configured + Mock.Get(_fileProvider).Setup(f => f.GetFileInfo(It.IsAny())).Returns((string s) => fileProvider.GetFileInfo(s)); + Mock.Get(_fileProvider).Setup(f => f.GetDirectoryContents(It.IsAny())).Returns((string s) => fileProvider.GetDirectoryContents(s)); + + var urls = await sut.GenerateCssUrlsAsync("test"); + + Assert.Equal("/sb/test.css.v00000", urls.FirstOrDefault()); + } + + + [Fact] + public async Task Generate_Css_Urls_Returns_Multiple_Urls_When_Debug_Profile_Is_Used() + { + var sut = new SmidgeHelper( + FakeProfileStrategy.DebugProfileStrategy, + _fileSetGenerator, + _dynamicallyRegisteredWebFiles, _preProcessManager, _fileSystemHelper, + _hasher, _bundleManager, _processorFactory, _urlManager, _requestHelper, + _httpContextAccessor, _cacheBusterResolver); + + _bundleManager.CreateCss("test", new[] + { + "file1.css", + "file2.css" + }); + + var dir = new InMemoryDirectory(); + dir.AddFile("", new StringFileInfo("File1", "file1.css")); + dir.AddFile("", new StringFileInfo("File2", "file2.css")); + var fileProvider = new InMemoryFileProvider(dir); + + // Configure the mock file provider to use the temporary file provider we've just configured + Mock.Get(_fileProvider).Setup(f => f.GetFileInfo(It.IsAny())).Returns((string s) => fileProvider.GetFileInfo(s)); + Mock.Get(_fileProvider).Setup(f => f.GetDirectoryContents(It.IsAny())).Returns((string s) => fileProvider.GetDirectoryContents(s)); + + var urls = await sut.GenerateJsUrlsAsync("test"); + + Assert.Equal("/file1.css?v=00000", urls.ElementAtOrDefault(0)); + Assert.Equal("/file2.css?v=00000", urls.ElementAtOrDefault(1)); + } + + + [Fact] + public async Task Generate_Css_Urls_Returns_Urls_With_Debug_Token_When_Debug_Parameter_Overrides_Profile() + { + var sut = new SmidgeHelper( + FakeProfileStrategy.DefaultProfileStrategy, + _fileSetGenerator, + _dynamicallyRegisteredWebFiles, _preProcessManager, _fileSystemHelper, + _hasher, _bundleManager, _processorFactory, _urlManager, _requestHelper, + _httpContextAccessor, _cacheBusterResolver); + + _bundleManager.CreateCss("test", new[] + { + "file1.css", + "file2.css" + }); + + var dir = new InMemoryDirectory(); + dir.AddFile("", new StringFileInfo("File1", "file1.css")); + dir.AddFile("", new StringFileInfo("File2", "file2.css")); + var fileProvider = new InMemoryFileProvider(dir); + + // Configure the mock file provider to use the temporary file provider we've just configured + Mock.Get(_fileProvider).Setup(f => f.GetFileInfo(It.IsAny())).Returns((string s) => fileProvider.GetFileInfo(s)); + Mock.Get(_fileProvider).Setup(f => f.GetDirectoryContents(It.IsAny())).Returns((string s) => fileProvider.GetDirectoryContents(s)); + + var urls = await sut.GenerateJsUrlsAsync("test", debug: true); + + Assert.Equal("/file1.css?d=00000", urls.ElementAtOrDefault(0)); + Assert.Equal("/file2.css?d=00000", urls.ElementAtOrDefault(1)); + } + + [Fact] public async Task Generate_Js_Urls_For_Non_Existent_Bundle_Throws_Exception() { var sut = new SmidgeHelper( + FakeProfileStrategy.DebugProfileStrategy, _fileSetGenerator, - _dynamicallyRegisteredWebFiles, _preProcessManager, _fileSystemHelper, + _dynamicallyRegisteredWebFiles, _preProcessManager, _fileSystemHelper, _hasher, _bundleManager, _processorFactory, _urlManager, _requestHelper, - _httpContextAccessor.Object, - new CacheBusterResolver(Enumerable.Empty())); + _httpContextAccessor, _cacheBusterResolver); var exception = await Assert.ThrowsAsync ( - async () => await sut.GenerateJsUrlsAsync("DoesntExist", true) + async () => await sut.GenerateJsUrlsAsync("DoesntExist") ); + } + + + + + [Fact] + public async Task Generate_Js_Urls_Returns_SingleBundleUrl_When_Default_Profile_Is_Used() + { + var sut = new SmidgeHelper( + FakeProfileStrategy.DefaultProfileStrategy, + _fileSetGenerator, + _dynamicallyRegisteredWebFiles, _preProcessManager, _fileSystemHelper, + _hasher, _bundleManager, _processorFactory, _urlManager, _requestHelper, + _httpContextAccessor, _cacheBusterResolver); + + _bundleManager.CreateJs("test", new[] + { + "file1.js", + "file2.js" + }); + + var dir = new InMemoryDirectory(); + dir.AddFile("", new StringFileInfo("File1", "file1.js")); + dir.AddFile("", new StringFileInfo("File2", "file2.js")); + var fileProvider = new InMemoryFileProvider(dir); + + // Configure the mock file provider to use the temporary file provider we've just configured + Mock.Get(_fileProvider).Setup(f => f.GetFileInfo(It.IsAny())).Returns((string s) => fileProvider.GetFileInfo(s)); + Mock.Get(_fileProvider).Setup(f => f.GetDirectoryContents(It.IsAny())).Returns((string s) => fileProvider.GetDirectoryContents(s)); + + var urls = await sut.GenerateJsUrlsAsync("test"); + + Assert.Equal("/sb/test.js.v00000", urls.FirstOrDefault()); + } + + + [Fact] + public async Task Generate_Js_Urls_Returns_Multiple_Urls_When_Debug_Profile_Is_Used() + { + var sut = new SmidgeHelper( + FakeProfileStrategy.DebugProfileStrategy, + _fileSetGenerator, + _dynamicallyRegisteredWebFiles, _preProcessManager, _fileSystemHelper, + _hasher, _bundleManager, _processorFactory, _urlManager, _requestHelper, + _httpContextAccessor, _cacheBusterResolver); + + _bundleManager.CreateJs("test", new[] + { + "file1.js", + "file2.js" + }); + + var dir = new InMemoryDirectory(); + dir.AddFile("", new StringFileInfo("File1", "file1.js")); + dir.AddFile("", new StringFileInfo("File2", "file2.js")); + var fileProvider = new InMemoryFileProvider(dir); + + // Configure the mock file provider to use the temporary file provider we've just configured + Mock.Get(_fileProvider).Setup(f => f.GetFileInfo(It.IsAny())).Returns((string s) => fileProvider.GetFileInfo(s)); + Mock.Get(_fileProvider).Setup(f => f.GetDirectoryContents(It.IsAny())).Returns((string s) => fileProvider.GetDirectoryContents(s)); + + var urls = await sut.GenerateJsUrlsAsync("test"); + + Assert.Equal("/file1.js?v=00000", urls.ElementAtOrDefault(0)); + Assert.Equal("/file2.js?v=00000", urls.ElementAtOrDefault(1)); + } + + + [Fact] + public async Task Generate_Js_Urls_Returns_Urls_With_Debug_Token_When_Debug_Parameter_Overrides_Profile() + { + var sut = new SmidgeHelper( + FakeProfileStrategy.DefaultProfileStrategy, + _fileSetGenerator, + _dynamicallyRegisteredWebFiles, _preProcessManager, _fileSystemHelper, + _hasher, _bundleManager, _processorFactory, _urlManager, _requestHelper, + _httpContextAccessor, _cacheBusterResolver); + + _bundleManager.CreateJs("test", new[] + { + "file1.js", + "file2.js" + }); + + var dir = new InMemoryDirectory(); + dir.AddFile("", new StringFileInfo("File1", "file1.js")); + dir.AddFile("", new StringFileInfo("File2", "file2.js")); + var fileProvider = new InMemoryFileProvider(dir); + // Configure the mock file provider to use the temporary file provider we've just configured + Mock.Get(_fileProvider).Setup(f => f.GetFileInfo(It.IsAny())).Returns((string s) => fileProvider.GetFileInfo(s)); + Mock.Get(_fileProvider).Setup(f => f.GetDirectoryContents(It.IsAny())).Returns((string s) => fileProvider.GetDirectoryContents(s)); + var urls = await sut.GenerateJsUrlsAsync("test", debug: true); + + Assert.Equal("/file1.js?d=00000", urls.ElementAtOrDefault(0)); + Assert.Equal("/file2.js?d=00000", urls.ElementAtOrDefault(1)); } + + [Fact] public async Task CssHere_HtmlString_For_Non_Existent_Css_Bundle_Throws_Exception() { var sut = new SmidgeHelper( + FakeProfileStrategy.DebugProfileStrategy, _fileSetGenerator, - _dynamicallyRegisteredWebFiles, _preProcessManager, _fileSystemHelper, + _dynamicallyRegisteredWebFiles, _preProcessManager, _fileSystemHelper, _hasher, _bundleManager, _processorFactory, _urlManager, _requestHelper, - _httpContextAccessor.Object, - new CacheBusterResolver(Enumerable.Empty())); + _httpContextAccessor, _cacheBusterResolver); var exception = await Assert.ThrowsAsync ( @@ -148,11 +355,11 @@ public async Task JsHere_HtmlString_For_Non_Existent_Css_Bundle_Throws_Exception { var sut = new SmidgeHelper( + FakeProfileStrategy.DebugProfileStrategy, _fileSetGenerator, - _dynamicallyRegisteredWebFiles, _preProcessManager, _fileSystemHelper, + _dynamicallyRegisteredWebFiles, _preProcessManager, _fileSystemHelper, _hasher, _bundleManager, _processorFactory, _urlManager, _requestHelper, - _httpContextAccessor.Object, - new CacheBusterResolver(Enumerable.Empty())); + _httpContextAccessor, _cacheBusterResolver); var exception = await Assert.ThrowsAsync (