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.

Gunnar Peipman

Gunnar Peipman is ASP.NET, Azure and SharePoint fan, Estonian Microsoft user group leader, blogger, conference speaker, teacher, and tech maniac. Since 2008 he is Microsoft MVP specialized on ASP.NET.

    2 thoughts on “General CSS path transform for ASP.NET bundling

    • July 19, 2019 at 4:30 pm
      Permalink

      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

    • January 14, 2020 at 8:29 pm
      Permalink

      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.

    Leave a Reply

    Your email address will not be published. Required fields are marked *