General CSS path transform for ASP.NET bundling
In couple of ASP.NET projects I have had a CSS path transform problems with bundling and minification. CSS-files are coming in from external agencies and from different open-source projects and the number of files is so big that it is problematic to go through all of these after updates. I found solution path transform solution that works for me in most cases and it is described here.
What CssRewriteUrlTransform exactly does?
It seems there is something up with CssRewriteUrlTransform class. From MSDN we can read the following about it: Rewrites urls to be absolute so assets will still be found after bundling. We will fnd out more from Process() method page: Example: bundle.Include(“~/content/some.css”) will transform url(images/1.jpg) => url(//static.gunnarpeipman.com/content/images/1.jpg).
If all CSS is under our control then we should be good to go with CssRewriteUrlTransform class. As I found out it doesn’t work in all cases. Specially if you have to bundle big number of CSS-files provided by some agency.
I have some web applications where tens of CSS-files are bundled and minified. The files come in as they are: some paths are relative to CSS-files and some paths are already absolute meaning that after publishing web application to some subfolder (some customers need it) some of files in design are not showing up anymore. It is unthinkable that after every CSS update I will go manually through all these files and format paths like CssRewriteUrlTransform class expects.
Custom bundle transform for CSS paths
After some digging around in internet I found some code samples and I ended up with the following path transforming class.
public class StyleRelativePathTransform : IBundleTransform
{
private static Regex pattern = new Regex(@"url\s*\(\s*([""']?)([^:)]+)\1\s*\)", RegexOptions.IgnoreCase);
public void Process(BundleContext context, BundleResponse response)
{
response.Content = string.Empty;
foreach (BundleFile file in response.Files)
{
using (var reader = new StreamReader(file.VirtualFile.Open()))
{
var contents = reader.ReadToEnd();
var matches = pattern.Matches(contents);
if (matches.Count > 0)
{
var directoryPath = VirtualPathUtility.GetDirectory(file.VirtualFile.VirtualPath);
foreach (Match match in matches)
{
var fileRelativePath = match.Groups[2].Value;
var fileVirtualPath = VirtualPathUtility.Combine(directoryPath, fileRelativePath);
var quote = match.Groups[1].Value;
var replace = String.Format("url({0}{1}{0})", quote, VirtualPathUtility.ToAbsolute(fileVirtualPath));
contents = contents.Replace(match.Groups[0].Value, replace);
}
}
response.Content = String.Format("{0}\r\n{1}", response.Content, contents);
}
}
}
}
It is able to handle URL-s in CSS-files in my case perfectly. Additional change is needed to bundling and minification. Let’s open BundleConfig class in App_Start folder and make styles section of RegisterBundles method look similar to the following code.
var styles = new StyleBundle("~/styles")
.Include("~/content/pages/meetings.min.css", new CssRewriteUrlTransform())
.Include("~/content/pages/login.min.css", new CssRewriteUrlTransform())
.Include("~/content/pages/error.min.css", new CssRewriteUrlTransform())
.Include("~/content/Site.css", new CssRewriteUrlTransform());
styles.Transforms.Insert(0, new StyleRelativePathTransform());
styles.Orderer = new AsIsBundleOrderer();
bundles.Add(styles);
AsIsBundeOrderer is another class found from web and it keeps CSS-files in order I added these to bundle. By default they are reordered for reasons unknown to me.
public class AsIsBundleOrderer : IBundleOrderer
{
public virtual IEnumerable<FileInfo> OrderFiles(BundleContext context, IEnumerable<FileInfo> files)
{
return files;
}
public IEnumerable<BundleFile> OrderFiles(BundleContext context, IEnumerable<BundleFile> files)
{
return files;
}
}
After applying the transform class and orderer given in this blog post my issues with long list of externally provided CSS-files were solved.
Thank you so much for this very useful resource. I used it for a similar problem and it solved very well.
Best regards
Ciro Corvino
I have been struggling with bundling for long time now, especially after deploying the code to IIS. Today I had this same problem and I decided it was time to find a fix for good. I then came across this page and got amazed. Thank you very much.