diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index c7be010..a0b943f 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -89,7 +89,3 @@ jobs:
with:
name: smidge-nuget-${{ env.GitVersion_SemVer }}
path: ${{ github.workspace }}/_NugetOutput/*.*
-
- - name: Publish to GitHub Packages
- if: ${{ success() && github.event_name == 'pull_request' }}
- run: dotnet nuget push "${{ github.workspace }}/_NugetOutput/*.nupkg" --api-key ${{ secrets.GITHUB_TOKEN }} --source "https://nuget.pkg.github.com/shazwazza/index.json"
diff --git a/Nuget.config b/Nuget.config
index 0e97909..f22d6f8 100644
--- a/Nuget.config
+++ b/Nuget.config
@@ -1,8 +1,6 @@
-
+
-
-
-
\ No newline at end of file
+
diff --git a/src/Directory.Build.props b/src/Directory.Build.props
index a0f6ec9..02590fd 100644
--- a/src/Directory.Build.props
+++ b/src/Directory.Build.props
@@ -9,7 +9,7 @@
9.0
-
+ https://github.com/Shazwazza/Smidge
@@ -23,6 +23,6 @@
4.0.0
- net7.0;net6.0;net5.0
+ net8.0;net6.0;
diff --git a/src/Smidge.Core/BundleManager.cs b/src/Smidge.Core/BundleManager.cs
index 9398272..202b924 100644
--- a/src/Smidge.Core/BundleManager.cs
+++ b/src/Smidge.Core/BundleManager.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Concurrent;
using Microsoft.Extensions.Options;
using Smidge.Models;
@@ -202,10 +202,12 @@ public void AddToBundle(string bundleName, JavaScriptFile file)
///
public Bundle GetBundle(string bundleName)
{
- Bundle collection;
- if (!TryGetValue(bundleName, out collection))
+ if (!TryGetValue(bundleName, out Bundle collection))
+ {
return null;
+ }
+
return collection;
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Smidge.Core/CompositeFiles/DefaultUrlManager.cs b/src/Smidge.Core/CompositeFiles/DefaultUrlManager.cs
index eed91dd..ca59c70 100644
--- a/src/Smidge.Core/CompositeFiles/DefaultUrlManager.cs
+++ b/src/Smidge.Core/CompositeFiles/DefaultUrlManager.cs
@@ -45,7 +45,7 @@ public string GetUrl(string bundleName, string fileExtension, bool debug, string
var handler = _keepFileExtensions ? "~/{0}/{1}.{3}{4}{2}" : "~/{0}/{1}{2}.{3}{4}";
return _requestHelper.Content(string.Format(handler,
_options.BundleFilePath,
- Uri.EscapeUriString(bundleName),
+ Uri.EscapeDataString(bundleName),
fileExtension,
debug ? 'd' : 'v',
cacheBusterValue));
@@ -167,9 +167,9 @@ private string GetCompositeUrl(string fileKey, string fileExtension, string cach
string.Format(
handler,
_options.CompositeFilePath,
- Uri.EscapeUriString(fileKey),
+ Uri.EscapeDataString(fileKey),
fileExtension,
cacheBusterValue));
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Smidge.Core/Smidge.Core.csproj b/src/Smidge.Core/Smidge.Core.csproj
index 6e91be9..cc6c7eb 100644
--- a/src/Smidge.Core/Smidge.Core.csproj
+++ b/src/Smidge.Core/Smidge.Core.csproj
@@ -14,12 +14,11 @@
-
-
-
-
-
-
-
+
+
+
+
+
+
diff --git a/src/Smidge.InMemory/Smidge.InMemory.csproj b/src/Smidge.InMemory/Smidge.InMemory.csproj
index 699e7e0..c0a23c6 100644
--- a/src/Smidge.InMemory/Smidge.InMemory.csproj
+++ b/src/Smidge.InMemory/Smidge.InMemory.csproj
@@ -12,7 +12,7 @@
-
+
diff --git a/src/Smidge.Nuglify/NuglifySourceMapController.cs b/src/Smidge.Nuglify/NuglifySourceMapController.cs
index 9d10932..aaba097 100644
--- a/src/Smidge.Nuglify/NuglifySourceMapController.cs
+++ b/src/Smidge.Nuglify/NuglifySourceMapController.cs
@@ -1,7 +1,8 @@
-using System;
+using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.ModelBinding.Binders;
using Smidge.Cache;
using Smidge.Models;
using Smidge.Options;
@@ -19,12 +20,11 @@ public NuglifySourceMapController(ISmidgeFileSystem fileSystem, IBundleManager b
_bundleManager = bundleManager;
}
- public FileResult SourceMap([FromServices] BundleRequestModel bundle)
+ public ActionResult SourceMap([FromServices] BundleRequestModel bundle)
{
- if (!_bundleManager.TryGetValue(bundle.FileKey, out _))
+ if (!bundle.IsBundleFound)
{
- //TODO: Throw an exception, this will result in an exception anyways
- return null;
+ return NotFound();
}
var sourceMapFile = _fileSystem.CacheFileSystem.GetRequiredFileInfo(bundle.GetSourceMapFilePath());
@@ -43,10 +43,9 @@ public FileResult SourceMap([FromServices] BundleRequestModel bundle)
}
}
- //TODO: Throw an exception, this will result in an exception anyways
- return null;
+ return NotFound();
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Smidge.Nuglify/Smidge.Nuglify.csproj b/src/Smidge.Nuglify/Smidge.Nuglify.csproj
index bbcc35c..33883c7 100644
--- a/src/Smidge.Nuglify/Smidge.Nuglify.csproj
+++ b/src/Smidge.Nuglify/Smidge.Nuglify.csproj
@@ -10,7 +10,7 @@
true
-
+
diff --git a/src/Smidge.Web/Startup.cs b/src/Smidge.Web/Startup.cs
index 0ab919a..bf120d4 100644
--- a/src/Smidge.Web/Startup.cs
+++ b/src/Smidge.Web/Startup.cs
@@ -198,6 +198,11 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
RequestPath = "/smidge-static"
});
+
+ bundles
+ .CreateCss("notfound-map-css-bundle",
+ "~/Css/notFoundMap.min.css"
+ );
});
app.UseSmidgeNuglify();
diff --git a/src/Smidge.Web/Views/Home/Index.cshtml b/src/Smidge.Web/Views/Home/Index.cshtml
index 393d56b..b63f708 100644
--- a/src/Smidge.Web/Views/Home/Index.cshtml
+++ b/src/Smidge.Web/Views/Home/Index.cshtml
@@ -34,6 +34,7 @@
+ @await SmidgeHelper.CssHereAsync("notfound-map-css-bundle", debug: false)
diff --git a/src/Smidge.Web/wwwroot/Css/notFoundMap.min.css b/src/Smidge.Web/wwwroot/Css/notFoundMap.min.css
new file mode 100644
index 0000000..7133bb1
--- /dev/null
+++ b/src/Smidge.Web/wwwroot/Css/notFoundMap.min.css
@@ -0,0 +1,2 @@
+@charset "UTF-8";
+ /*# sourceMappingURL=notFound.min.css.map */
diff --git a/src/Smidge/Controllers/AddCompressionHeaderAttribute.cs b/src/Smidge/Controllers/AddCompressionHeaderAttribute.cs
index f2ca3fb..f69f65d 100644
--- a/src/Smidge/Controllers/AddCompressionHeaderAttribute.cs
+++ b/src/Smidge/Controllers/AddCompressionHeaderAttribute.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Linq;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.DependencyInjection;
@@ -55,7 +55,7 @@ public void OnActionExecuted(ActionExecutedContext context)
if (context.Exception != null) return;
//get the model from the items
- if (context.HttpContext.Items.TryGetValue(nameof(AddCompressionHeaderAttribute), out var requestModel) && requestModel is RequestModel file)
+ if (context.HttpContext.Items.TryGetValue(nameof(AddCompressionHeaderAttribute), out var requestModel) && requestModel is RequestModel file && file.IsBundleFound)
{
var enableCompression = true;
@@ -72,4 +72,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..cf08dae 100644
--- a/src/Smidge/Controllers/AddExpiryHeadersAttribute.cs
+++ b/src/Smidge/Controllers/AddExpiryHeadersAttribute.cs
@@ -50,7 +50,7 @@ public void OnActionExecuted(ActionExecutedContext context)
return;
//get the model from the items
- if (!context.HttpContext.Items.TryGetValue(nameof(AddExpiryHeadersAttribute), out object fileObject) || fileObject is not RequestModel file)
+ if (!context.HttpContext.Items.TryGetValue(nameof(AddExpiryHeadersAttribute), out object fileObject) || fileObject is not RequestModel file || !file.IsBundleFound)
return;
var enableETag = true;
diff --git a/src/Smidge/Controllers/CheckNotModifiedAttribute.cs b/src/Smidge/Controllers/CheckNotModifiedAttribute.cs
index 8229ea5..8fb90eb 100644
--- a/src/Smidge/Controllers/CheckNotModifiedAttribute.cs
+++ b/src/Smidge/Controllers/CheckNotModifiedAttribute.cs
@@ -1,4 +1,4 @@
-using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using Smidge.Models;
using System;
@@ -51,7 +51,7 @@ public void OnActionExecuted(ActionExecutedContext context)
if (context.Exception != null) return;
//get the model from the items
- if (context.HttpContext.Items.TryGetValue(nameof(CheckNotModifiedAttribute), out var requestModel) && requestModel is RequestModel file)
+ if (context.HttpContext.Items.TryGetValue(nameof(CheckNotModifiedAttribute), out var requestModel) && requestModel is RequestModel file && file.IsBundleFound)
{
//Don't execute when the request is in Debug
if (file.Debug)
@@ -74,4 +74,4 @@ private static void ReturnNotModified(ActionExecutedContext context)
}
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Smidge/Controllers/CompositeFileCacheFilterAttribute.cs b/src/Smidge/Controllers/CompositeFileCacheFilterAttribute.cs
index 674f7d0..2d3e5ec 100644
--- a/src/Smidge/Controllers/CompositeFileCacheFilterAttribute.cs
+++ b/src/Smidge/Controllers/CompositeFileCacheFilterAttribute.cs
@@ -68,7 +68,7 @@ public void OnActionExecuting(ActionExecutingContext context)
if (context.ActionArguments.Count == 0) return;
var firstArg = context.ActionArguments.First().Value;
- if (firstArg is RequestModel file)
+ if (firstArg is RequestModel file && file.IsBundleFound)
{
var cacheBusterValue = file.ParsedPath.CacheBusterValue;
diff --git a/src/Smidge/Controllers/SmidgeController.cs b/src/Smidge/Controllers/SmidgeController.cs
index 3df51b0..f516aa4 100644
--- a/src/Smidge/Controllers/SmidgeController.cs
+++ b/src/Smidge/Controllers/SmidgeController.cs
@@ -1,18 +1,19 @@
using System;
-using Microsoft.AspNetCore.Mvc;
-using Smidge.CompositeFiles;
-using Smidge.Models;
+using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
-using System.IO.Compression;
using System.Linq;
+using System.Threading;
using System.Threading.Tasks;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Logging;
-using Smidge.FileProcessors;
using Smidge.Cache;
-using Microsoft.AspNetCore.Authorization;
+using Smidge.CompositeFiles;
+using Smidge.FileProcessors;
+using Smidge.Models;
namespace Smidge.Controllers
{
@@ -27,6 +28,8 @@ namespace Smidge.Controllers
[AllowAnonymous]
public class SmidgeController : Controller
{
+ private static readonly ConcurrentDictionary s_locks = new ConcurrentDictionary();
+
private readonly ISmidgeFileSystem _fileSystem;
private readonly IBundleManager _bundleManager;
private readonly IBundleFileSetGenerator _fileSetGenerator;
@@ -67,51 +70,46 @@ public SmidgeController(
public async Task Bundle(
[FromServices] BundleRequestModel bundleModel)
{
- if (!_bundleManager.TryGetValue(bundleModel.FileKey, out Bundle foundBundle))
+ if (!bundleModel.IsBundleFound || !_bundleManager.TryGetValue(bundleModel.FileKey, out Bundle foundBundle))
{
return NotFound();
}
- var bundleOptions = foundBundle.GetBundleOptions(_bundleManager, bundleModel.Debug);
-
- var cacheBusterValue = bundleModel.ParsedPath.CacheBusterValue;
+ Options.BundleOptions bundleOptions = foundBundle.GetBundleOptions(_bundleManager, bundleModel.Debug);
- //now we need to determine if this bundle has already been created
- var cacheFile = _fileSystem.CacheFileSystem.GetCachedCompositeFile(cacheBusterValue, bundleModel.Compression, bundleModel.FileKey, out var cacheFilePath);
- if (cacheFile.Exists)
+ if (TryGetBundle(bundleModel, out IActionResult actionResult, out var cacheFilePath))
{
- _logger.LogDebug($"Returning bundle '{bundleModel.FileKey}' from cache");
-
+ return actionResult;
+ }
- if (!string.IsNullOrWhiteSpace(cacheFile.PhysicalPath))
+ SemaphoreSlim bundleLock = s_locks.GetOrAdd(foundBundle.Name, s => new SemaphoreSlim(1, 1));
+ await bundleLock.WaitAsync();
+ try
+ {
+ // Double check, might be available now
+ if (TryGetBundle(bundleModel, out actionResult, out _))
{
- //if physical path is available then it's the physical file system, in which case we'll deliver the file with the PhysicalFileResult
- //FilePathResult uses IHttpSendFileFeature which is a native host option for sending static files
- return PhysicalFile(cacheFile.PhysicalPath, bundleModel.Mime);
+ return actionResult;
}
- else
+
+ //the bundle doesn't exist so we'll go get the files, process them and create the bundle
+
+ //get the files for the bundle
+ IWebFile[] files = _fileSetGenerator.GetOrderedFileSet(foundBundle,
+ _processorFactory.CreateDefault(
+ //the file type in the bundle will always be the same
+ foundBundle.Files[0].DependencyType))
+ .ToArray();
+
+ if (files.Length == 0)
{
- return File(cacheFile.CreateReadStream(), bundleModel.Mime);
+ return NotFound();
}
- }
- //the bundle doesn't exist so we'll go get the files, process them and create the bundle
- //TODO: We should probably lock here right?! we don't want multiple threads trying to do this at the same time, we'll need a dictionary of locks to do this effectively
+ var cacheBusterValue = bundleModel.ParsedPath.CacheBusterValue;
- //get the files for the bundle
- var files = _fileSetGenerator.GetOrderedFileSet(foundBundle,
- _processorFactory.CreateDefault(
- //the file type in the bundle will always be the same
- foundBundle.Files[0].DependencyType))
- .ToArray();
-
- if (files.Length == 0)
- {
- return NotFound();
- }
+ using var bundleContext = new BundleContext(cacheBusterValue, bundleModel, cacheFilePath);
- using (var bundleContext = new BundleContext(cacheBusterValue, bundleModel, cacheFilePath))
- {
var watch = new Stopwatch();
watch.Start();
_logger.LogDebug($"Processing bundle '{bundleModel.FileKey}', debug? {bundleModel.Debug} ...");
@@ -123,7 +121,7 @@ public async Task Bundle(
}
//Get each file path to it's hashed location since that is what the pre-processed file will be saved as
- var fileInfos = files.Select(x => _fileSystem.CacheFileSystem.GetCacheFile(
+ IEnumerable fileInfos = files.Select(x => _fileSystem.CacheFileSystem.GetCacheFile(
x,
() => _fileSystem.GetRequiredFileInfo(x),
bundleOptions.FileWatchOptions.Enabled,
@@ -131,23 +129,30 @@ public async Task Bundle(
cacheBusterValue,
out _));
- using (var resultStream = await GetCombinedStreamAsync(fileInfos, bundleContext))
- {
- //compress the response (if enabled)
- //do not compress anything if it's not enabled in the bundle options
- var compressedStream = await Compressor.CompressAsync(bundleOptions.CompressResult ? bundleModel.Compression : CompressionType.None,
- bundleOptions.CompressionLevel,
- resultStream);
-
- //save the resulting compressed file, if compression is not enabled it will just save the non compressed format
- // this persisted file will be used in the CheckNotModifiedAttribute which will short circuit the request and return
- // the raw file if it exists for further requests to this path
- await CacheCompositeFileAsync(_fileSystem.CacheFileSystem, cacheFilePath, compressedStream);
+ using Stream resultStream = await GetCombinedStreamAsync(fileInfos, bundleContext);
- _logger.LogDebug($"Processed bundle '{bundleModel.FileKey}' in {watch.ElapsedMilliseconds}ms");
+ //compress the response (if enabled)
+ //do not compress anything if it's not enabled in the bundle options
+ Stream compressedStream = await Compressor.CompressAsync(bundleOptions.CompressResult ? bundleModel.Compression : CompressionType.None,
+ bundleOptions.CompressionLevel,
+ resultStream);
- //return the stream
- return File(compressedStream, bundleModel.Mime);
+ //save the resulting compressed file, if compression is not enabled it will just save the non compressed format
+ // this persisted file will be used in the CheckNotModifiedAttribute which will short circuit the request and return
+ // the raw file if it exists for further requests to this path
+ await CacheCompositeFileAsync(_fileSystem.CacheFileSystem, cacheFilePath, compressedStream);
+
+ _logger.LogDebug($"Processed bundle '{bundleModel.FileKey}' in {watch.ElapsedMilliseconds}ms");
+
+ //return the stream
+ return File(compressedStream, bundleModel.Mime);
+ }
+ finally
+ {
+ // Remove the lock from the dictionary and release the lock.
+ if (s_locks.TryRemove(foundBundle.Name, out SemaphoreSlim lck))
+ {
+ lck.Release();
}
}
}
@@ -160,7 +165,7 @@ public async Task Bundle(
public async Task Composite(
[FromServices] CompositeFileModel file)
{
- if (!file.ParsedPath.Names.Any())
+ if (!file.IsBundleFound || !file.ParsedPath.Names.Any())
{
return NotFound();
}
@@ -201,6 +206,35 @@ public async Task Composite(
}
}
+ private bool TryGetBundle(BundleRequestModel bundleModel, out IActionResult actionResult, out string cacheFilePath)
+ {
+ var cacheBusterValue = bundleModel.ParsedPath.CacheBusterValue;
+
+ //now we need to determine if this bundle has already been created
+ IFileInfo cacheFile = _fileSystem.CacheFileSystem.GetCachedCompositeFile(cacheBusterValue, bundleModel.Compression, bundleModel.FileKey, out cacheFilePath);
+ if (cacheFile.Exists)
+ {
+ _logger.LogDebug($"Returning bundle '{bundleModel.FileKey}' from cache");
+
+
+ if (!string.IsNullOrWhiteSpace(cacheFile.PhysicalPath))
+ {
+ //if physical path is available then it's the physical file system, in which case we'll deliver the file with the PhysicalFileResult
+ //FilePathResult uses IHttpSendFileFeature which is a native host option for sending static files
+ actionResult = PhysicalFile(cacheFile.PhysicalPath, bundleModel.Mime);
+ return true;
+ }
+ else
+ {
+ actionResult = File(cacheFile.CreateReadStream(), bundleModel.Mime);
+ return true;
+ }
+ }
+
+ actionResult = null;
+ return false;
+ }
+
private static async Task CacheCompositeFileAsync(ICacheFileSystem cacheProvider, string filePath, Stream compositeStream)
{
await cacheProvider.WriteFileAsync(filePath, compositeStream);
diff --git a/src/Smidge/Models/BundleRequestModel.cs b/src/Smidge/Models/BundleRequestModel.cs
index 337144f..c5f3772 100644
--- a/src/Smidge/Models/BundleRequestModel.cs
+++ b/src/Smidge/Models/BundleRequestModel.cs
@@ -1,4 +1,4 @@
-using Smidge.CompositeFiles;
+using Smidge.CompositeFiles;
using System;
using System.Linq;
using Microsoft.AspNetCore.Mvc.Infrastructure;
@@ -18,17 +18,25 @@ public BundleRequestModel(IUrlManager urlManager, IActionContextAccessor accesso
// In reality we'll need to do that anyways if we want to support load balancing!
// https://github.com/Shazwazza/Smidge/issues/17
+ if (!IsBundleFound)
+ {
+ return;
+ }
if (!ParsedPath.Names.Any())
{
- throw new InvalidOperationException("The bundle route value does not contain a bundle name");
+ IsBundleFound = false;
+
+ return;
}
FileKey = ParsedPath.Names.Single();
if (!bundleManager.TryGetValue(FileKey, out Bundle bundle))
{
- throw new InvalidOperationException("No bundle found with key " + FileKey);
+ IsBundleFound = false;
+
+ return;
}
Bundle = bundle;
}
@@ -36,4 +44,4 @@ public BundleRequestModel(IUrlManager urlManager, IActionContextAccessor accesso
public Bundle Bundle { get; }
public override string FileKey { get; }
}
-}
\ No newline at end of file
+}
diff --git a/src/Smidge/Models/CompositeFileModel.cs b/src/Smidge/Models/CompositeFileModel.cs
index d800fc9..1d850fe 100644
--- a/src/Smidge/Models/CompositeFileModel.cs
+++ b/src/Smidge/Models/CompositeFileModel.cs
@@ -1,4 +1,4 @@
-using Smidge.CompositeFiles;
+using Smidge.CompositeFiles;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Smidge.Hashing;
@@ -10,10 +10,14 @@ public class CompositeFileModel : RequestModel
public CompositeFileModel(IHasher hasher, IUrlManager urlManager, IActionContextAccessor accessor, IRequestHelper requestHelper)
: base("file", urlManager, accessor, requestHelper)
{
+ if (!IsBundleFound)
+ {
+ return;
+ }
//Creates a single hash of the full url (which can include many files)
FileKey = hasher.Hash(string.Join(".", ParsedPath.Names));
}
public override string FileKey { get; }
}
-}
\ No newline at end of file
+}
diff --git a/src/Smidge/Models/RequestModel.cs b/src/Smidge/Models/RequestModel.cs
index 9704611..d439783 100644
--- a/src/Smidge/Models/RequestModel.cs
+++ b/src/Smidge/Models/RequestModel.cs
@@ -1,4 +1,4 @@
-using Smidge.CompositeFiles;
+using Smidge.CompositeFiles;
using System;
using Microsoft.AspNetCore.Mvc.Infrastructure;
@@ -25,7 +25,10 @@ protected RequestModel(string valueName, IUrlManager urlManager, IActionContextA
ParsedPath = urlManager.ParsePath(bundleId);
if (ParsedPath == null)
- throw new InvalidOperationException($"Could not parse {bundleId} as a valid smidge path");
+ {
+ IsBundleFound = false;
+ return;
+ }
Debug = ParsedPath.Debug;
@@ -61,5 +64,7 @@ protected RequestModel(string valueName, IUrlManager urlManager, IActionContextA
public string Mime { get; private set; }
public DateTime LastFileWriteTime { get; set; }
+
+ public bool IsBundleFound { get; set; } = true;
}
-}
\ No newline at end of file
+}
diff --git a/src/Smidge/Properties/launchSettings.json b/src/Smidge/Properties/launchSettings.json
new file mode 100644
index 0000000..ec82ba1
--- /dev/null
+++ b/src/Smidge/Properties/launchSettings.json
@@ -0,0 +1,12 @@
+{
+ "profiles": {
+ "Smidge": {
+ "commandName": "Project",
+ "launchBrowser": true,
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ },
+ "applicationUrl": "https://localhost:62980;http://localhost:62981"
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Smidge/SmidgeHelper.cs b/src/Smidge/SmidgeHelper.cs
index 3cb54a8..1135798 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;
@@ -173,14 +173,12 @@ private IEnumerable GenerateBundleUrlsAsync(string bundleName, string fi
//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
- var bundle = _bundleManager.GetBundle(bundleName);
- if (bundle == null)
- {
- throw new BundleNotFoundException(bundleName);
- }
+ var bundle = _bundleManager.GetBundle(bundleName) ?? throw new BundleNotFoundException(bundleName);
if (bundle.Files.Count == 0)
+ {
return Enumerable.Empty();
+ }
var result = new List();
diff --git a/src/Smidge/TagHelpers/SmidgeLinkTagHelper.cs b/src/Smidge/TagHelpers/SmidgeLinkTagHelper.cs
index 239031a..fdae1ed 100644
--- a/src/Smidge/TagHelpers/SmidgeLinkTagHelper.cs
+++ b/src/Smidge/TagHelpers/SmidgeLinkTagHelper.cs
@@ -1,3 +1,4 @@
+using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.Encodings.Web;
@@ -5,43 +6,31 @@
using Microsoft.AspNetCore.Html;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Razor.TagHelpers;
-using Microsoft.AspNetCore.Mvc.TagHelpers;
-using System.Collections.Generic;
-using System;
namespace Smidge.TagHelpers
{
[HtmlTargetElement("link", Attributes = HrefAttributeName, TagStructure = TagStructure.WithoutEndTag)]
public class SmidgeLinkTagHelper : TagHelper
{
- private const string HrefIncludeAttributeName = "asp-href-include";
- private const string HrefExcludeAttributeName = "asp-href-exclude";
- private const string FallbackHrefAttributeName = "asp-fallback-href";
- private const string SuppressFallbackIntegrityAttributeName = "asp-suppress-fallback-integrity";
- private const string FallbackHrefIncludeAttributeName = "asp-fallback-href-include";
- private const string FallbackHrefExcludeAttributeName = "asp-fallback-href-exclude";
- private const string FallbackTestClassAttributeName = "asp-fallback-test-class";
- private const string FallbackTestPropertyAttributeName = "asp-fallback-test-property";
- private const string FallbackTestValueAttributeName = "asp-fallback-test-value";
- private const string AppendVersionAttributeName = "asp-append-version";
private const string HrefAttributeName = "href";
+ private readonly IBundleManager _bundleManager;
+ private readonly HtmlEncoder _encoder;
- private readonly HashSet _invalid = new()
+ private readonly HashSet _invalidAttributes = new()
{
- HrefIncludeAttributeName,
- HrefExcludeAttributeName,
- FallbackHrefAttributeName,
- FallbackHrefIncludeAttributeName,
- FallbackTestClassAttributeName,
- FallbackTestPropertyAttributeName,
- SuppressFallbackIntegrityAttributeName,
- FallbackHrefExcludeAttributeName,
- FallbackTestValueAttributeName,
- AppendVersionAttributeName
+ "asp-href-include",
+ "asp-href-exclude",
+ "asp-fallback-href",
+ "asp-fallback-href-exclude",
+ "asp-fallback-test-class",
+ "asp-fallback-test-property",
+ "asp-fallback-test-value",
+ "asp-suppress-fallback-integrity",
+ "asp-suppress-fallback-integrity",
+ "asp-append-version"
};
+
private readonly SmidgeHelper _smidgeHelper;
- private readonly IBundleManager _bundleManager;
- private readonly HtmlEncoder _encoder;
public SmidgeLinkTagHelper(SmidgeHelper smidgeHelper, IBundleManager bundleManager, HtmlEncoder encoder)
{
@@ -50,10 +39,13 @@ public SmidgeLinkTagHelper(SmidgeHelper smidgeHelper, IBundleManager bundleManag
_encoder = encoder;
}
+ [HtmlAttributeName("debug")]
+ public bool Debug { get; set; }
+
///
- /// TODO: Need to figure out why we need this. If the order is default and executes 'after' the
+ /// 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
- /// doesn't actually work, it simply doesn't get passed through. Not sure if this is a bug or if I'm
+ /// doesn't actually work, it simply doesn't get passed through. Not sure if this is a bug or if I'm
/// doing it wrong. In the meantime, setting this to execute before the defaults works.
///
public override int Order => -2000;
@@ -61,9 +53,6 @@ public SmidgeLinkTagHelper(SmidgeHelper smidgeHelper, IBundleManager bundleManag
[HtmlAttributeName(HrefAttributeName)]
public string Source { get; set; }
- [HtmlAttributeName("debug")]
- public bool Debug { get; set; }
-
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
if (string.IsNullOrWhiteSpace(Source))
@@ -71,7 +60,7 @@ public override async Task ProcessAsync(TagHelperContext context, TagHelperOutpu
return;
}
- var exists = _bundleManager.Exists(Source);
+ bool exists = _bundleManager.Exists(Source);
// Pass through attribute that is also a well-known HTML attribute.
// this is required to make sure that other tag helpers executing against this element have
@@ -83,29 +72,32 @@ public override async Task ProcessAsync(TagHelperContext context, TagHelperOutpu
return;
}
- if (context.AllAttributes.Any(x => _invalid.Contains(x.Name)))
+ if (context.AllAttributes.Any(x => _invalidAttributes.Contains(x.Name)))
+ {
+ return;
+ }
+
+ if (context.AllAttributes.TryGetAttribute("as", out TagHelperAttribute attribute) && attribute.Value is not "style")
{
- throw new InvalidOperationException("Smidge tag helpers do not support the ASP.NET tag helpers: " + string.Join(", ", _invalid));
+ return;
}
- var result = (await _smidgeHelper.GenerateCssUrlsAsync(Source, Debug)).ToArray();
- var currAttr = output.Attributes.ToDictionary(x => x.Name, x => x.Value);
- using (var writer = new StringWriter())
+ var attributes = output.Attributes.ToDictionary(x => x.Name, x => x.Value);
+ await using (var writer = new StringWriter())
{
- foreach (var s in result)
+ foreach (var url in await _smidgeHelper.GenerateCssUrlsAsync(Source, Debug))
{
- var builder = new TagBuilder(output.TagName)
- {
- TagRenderMode = TagRenderMode.SelfClosing
- };
- builder.MergeAttributes(currAttr);
- builder.Attributes["href"] = s;
+ var builder = new TagBuilder(output.TagName) { TagRenderMode = TagRenderMode.SelfClosing };
+ builder.MergeAttributes(attributes);
+ builder.Attributes["href"] = url;
builder.WriteTo(writer, _encoder);
}
- writer.Flush();
+
+ await writer.FlushAsync();
output.PostElement.SetHtmlContent(new HtmlString(writer.ToString()));
}
+
//This ensures the original tag is not written.
output.TagName = null;
}
diff --git a/test/Smidge.Benchmarks/Smidge.Benchmarks.csproj b/test/Smidge.Benchmarks/Smidge.Benchmarks.csproj
index ca714e4..d66d353 100644
--- a/test/Smidge.Benchmarks/Smidge.Benchmarks.csproj
+++ b/test/Smidge.Benchmarks/Smidge.Benchmarks.csproj
@@ -1,7 +1,7 @@
-
+
- net5.0
+ net8.0Smidge.BenchmarksExeSmidge.Benchmarks
@@ -18,9 +18,9 @@
-
-
-
+
+
+
diff --git a/test/Smidge.Tests/Smidge.Tests.csproj b/test/Smidge.Tests/Smidge.Tests.csproj
index ed586e4..3afdc59 100644
--- a/test/Smidge.Tests/Smidge.Tests.csproj
+++ b/test/Smidge.Tests/Smidge.Tests.csproj
@@ -1,7 +1,7 @@
- net6.0;net5.0
+ net8.0;net6.0Smidge.TestsSmidge.Testsfalse
@@ -14,14 +14,14 @@
-
-
-
+
+
+ allruntime; build; native; contentfiles; analyzers; buildtransitive
-
-
+
+