CssRewriteUrlTransform with or without virtual directory

C#asp.net Mvc-4Bundling and-MinificationWeb Optimization

C# Problem Overview


We are using MVC Bundling in our site, CssRewriteUrlTransform makes sure that the image urls work from the dynamic bundle css file.

But this only works when not using a virtual directory, i.e

http://localhost/VirttualDir does not work but http://localhost/ does. This is because the CssRewriteUrlTransform tranform does not take the virtual folder into account when rewriting the url. So if a image real path is localhost/vdir/content/img/foo.png it will rewrite it to localhost/content/img/foo.png which is wrong

C# Solutions


Solution 1 - C#

I am not sure to fully understand your problem, but seeing http://localhost here seems wrong. You should never use an absolute URL for your bundles.

For me CssRewriteUrlTransform works perfectly, here is how I use it:

bundles.Add(new StyleBundle("~/bundles/css").Include(
                "~/Content/css/*.css", new CssRewriteUrlTransform()));

"Bundles" is virtual.

Does this helps?

Update

I was confused with the "VirtualDir" thing, as you are talking about IIS VirtualDir, and I was thinking Bundle VirtualDir! It's true that in this case CssRewriteUrlTransform will rewrite URLs to the Host, not to the Host/VirtualDir URI.

To do that, you have to derive CssRewriteUrlTransform to make it do what you need it to. There is a good discussion here: https://stackoverflow.com/questions/12577108/asp-net-mvc4-bundling-with-twitter-bootstrap?rq=1

Seems the best answer is there:http://aspnetoptimization.codeplex.com/workitem/83

public class CssRewriteUrlTransformWrapper : IItemTransform
{
    public string Process(string includedVirtualPath, string input)
    {           
        return new CssRewriteUrlTransform().Process("~" + VirtualPathUtility.ToAbsolute(includedVirtualPath), input);           
    }
}

Use this class instead of CssRewriteUrlTransform

Solution 2 - C#

I had the same problem. This is how I fixed it:

private class ProperUrlRewrite : IItemTransform
{
    private static string RebaseUrlToAbsolute(string baseUrl, string url)
    {
        if (string.IsNullOrWhiteSpace(url) || string.IsNullOrWhiteSpace(baseUrl) || url.StartsWith("/", StringComparison.OrdinalIgnoreCase) || url.Contains(':'))
            return url;
        return VirtualPathUtility.Combine(baseUrl, url);
    }
    private static Regex UrlPattern = new Regex("url\\s*\\(['\"]?(?<url>[^)]+?)['\"]?\\)");
    public string Process(string includedVirtualPath, string input)
    {
        if (includedVirtualPath == null)
            throw new ArgumentNullException("includedVirtualPath");
        if (string.IsNullOrWhiteSpace(input))
            return input;

        string directory = VirtualPathUtility.GetDirectory(VirtualPathUtility.ToAbsolute(includedVirtualPath));
        if (!directory.EndsWith("/", StringComparison.OrdinalIgnoreCase))
            directory += "/";
        return UrlPattern.Replace(input, match => "url(" + ProperUrlRewrite.RebaseUrlToAbsolute(directory, match.Groups["url"].Value) + ")");
    }
}

I know it's far from perfect and there are plenty of edge cases where this can go wrong (I'm not sure you can parse a CSS file with a regex in the first place - though this is exactly what the original CssRewriteUrlTransform does), but so far it holds...

Solution 3 - C#

The 'CssRewriteUrlTransform' works just fine for applications that DOESN'T run on top of a virtual directory.

So, if your app runs on http://your-site.com/ it runs just fine, but if runs on http://your-site.com/your-app/ you'll have 404 for all your images, because the default 'CssFixRewriteUrlTransform' is referencing your images with a '/'.

Use this:

public class CssFixRewriteUrlTransform: IItemTransform {

	private static string ConvertUrlsToAbsolute(string baseUrl, string content) {
		if (string.IsNullOrWhiteSpace(content)) {
			return content;
		}
		var regex = new Regex("url\\(['\"]?(?<url>[^)]+?)['\"]?\\)");
		return regex.Replace(content, match = > string.Concat("url(", RebaseUrlToAbsolute(baseUrl, match.Groups["url"].Value), ")"));
	}

	public string Process(string includedVirtualPath, string input) {
		if (includedVirtualPath == null) {
			throw new ArgumentNullException("includedVirtualPath");
		}
		var directory = VirtualPathUtility.GetDirectory(includedVirtualPath);
		return ConvertUrlsToAbsolute(directory, input);
	}

	private static string RebaseUrlToAbsolute(string baseUrl, string url) {
		if (string.IsNullOrWhiteSpace(url) || string.IsNullOrWhiteSpace(baseUrl) || url.StartsWith("/", StringComparison.OrdinalIgnoreCase)) {
			return url;
		}
		if (!baseUrl.EndsWith("/", StringComparison.OrdinalIgnoreCase)) {
			baseUrl = string.Concat(baseUrl, "/");
		}
		return VirtualPathUtility.ToAbsolute(string.Concat(baseUrl, url));
	}
}

Note: delete all file css with .min.css, because if don't it doesn't fix.

Solution 4 - C#

I have problems with url that contains "data" and even a url inner another one, so I have to re-do the regex, this is my solution:

public string Process(string includedVirtualPath, string input)
    {
        if (includedVirtualPath == null)
        {
            throw new ArgumentNullException(nameof(includedVirtualPath));
        }

        if (string.IsNullOrWhiteSpace(input))
        {
            return input;
        }

        var directory = VirtualPathUtility.GetDirectory(includedVirtualPath);
        if (!directory.EndsWith("/", StringComparison.OrdinalIgnoreCase))
        {
            directory += "/";
        }

        return new Regex(@"url\s*\(\s*([\'""]?)(?<scheme>(?:(?:data:)|(?:https?:))?)(?<url>(\\\1|.)*?)\1\s*\)")
            .Replace(input, match => string.Concat(
                "url(",
                match.Groups[1].Value,
                match.Groups["scheme"].Value,
                match.Groups["scheme"].Value == "" ?
                    RebaseUrlToAbsolute(directory, match.Groups["url"].Value) :
                    match.Groups["url"].Value,
                match.Groups[1].Value,
                ")"
            ));
    }

    private static string RebaseUrlToAbsolute(string baseUrl, string url)
    {
        if (string.IsNullOrWhiteSpace(url) || string.IsNullOrWhiteSpace(baseUrl)
            || url.StartsWith("/", StringComparison.OrdinalIgnoreCase))
        {
            return url;
        }
        
        return VirtualPathUtility.ToAbsolute(string.Concat(baseUrl, url));
    }
}

based on https://stackoverflow.com/questions/171480/regex-grabbing-values-between-quotation-marks/34198968#34198968

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionAndersView Question on Stackoverflow
Solution 1 - C#BernardGView Answer on Stackoverflow
Solution 2 - C#Vilx-View Answer on Stackoverflow
Solution 3 - C#Phan Đức BìnhView Answer on Stackoverflow
Solution 4 - C#cflorenciavView Answer on Stackoverflow