MVC4 StyleBundle not resolving images

2019-01-01 11:41发布

My question is similar to this:

ASP.NET MVC 4 Minification & Background Images

Except that I want to stick with MVC's own bundling if I can. I'm having a brain crash trying to figure out what the correct pattern is for specifying style bundles such that standalone css and image sets such as jQuery UI work.

I have a typical MVC site structure with /Content/css/ which contains my base CSS such as styles.css. Within that css folder I also have subfolders such as /jquery-ui which contains its CSS file plus an /images folder. Image paths in the jQuery UI CSS are relative to that folder and I don't want to mess with them.

As I understand it, when I specify a StyleBundle I need to specify a virtual path which does not also match a real content path, because (assuming I'm ignoring routes to Content) IIS would then try to resolve that path as a physical file. So I'm specifying:

bundles.Add(new StyleBundle("~/Content/styles/jquery-ui")
       .Include("~/Content/css/jquery-ui/*.css"));

rendered using:

@Styles.Render("~/Content/styles/jquery-ui")

I can see the request going out to:

http://localhost/MySite/Content/styles/jquery-ui?v=nL_6HPFtzoqrts9nwrtjq0VQFYnhMjY5EopXsK8cxmg1

This is returning the correct, minified CSS response. But then the browser sends a request for a relatively linked image as:

http://localhost/MySite/Content/styles/images/ui-bg_highlight-soft_100_eeeeee_1x100.png

Which is a 404.

I understand that the last part of my URL jquery-ui is an extensionless URL, a handler for my bundle, so I can see why the relative request for the image is simply /styles/images/.

So my question is what is the correct way of handling this situation?

15条回答
回忆,回不去的记忆
2楼-- · 2019-01-01 12:28

Grinn solution is great.

However it doesn't work for me when there are parent folder relative references in the url. i.e. url('../../images/car.png')

So, I slightly changed the Include method in order to resolve the paths for each regex match, allowing relative paths and also to optionally embed the images in the css.

I also changed the IF DEBUG to check BundleTable.EnableOptimizations instead of HttpContext.Current.IsDebuggingEnabled.

    public new Bundle Include(params string[] virtualPaths)
    {
        if (!BundleTable.EnableOptimizations)
        {
            // Debugging. Bundling will not occur so act normal and no one gets hurt. 
            base.Include(virtualPaths.ToArray());
            return this;
        }
        var bundlePaths = new List<string>();
        var server = HttpContext.Current.Server;
        var pattern = new Regex(@"url\s*\(\s*([""']?)([^:)]+)\1\s*\)", RegexOptions.IgnoreCase);
        foreach (var path in virtualPaths)
        {
            var contents = File.ReadAllText(server.MapPath(path));
            var matches = pattern.Matches(contents);
            // Ignore the file if no matches
            if (matches.Count == 0)
            {
                bundlePaths.Add(path);
                continue;
            }
            var bundlePath = (System.IO.Path.GetDirectoryName(path) ?? string.Empty).Replace(@"\", "/") + "/";
            var bundleUrlPath = VirtualPathUtility.ToAbsolute(bundlePath);
            var bundleFilePath = string.Format("{0}{1}.bundle{2}",
                                               bundlePath,
                                               System.IO.Path.GetFileNameWithoutExtension(path),
                                               System.IO.Path.GetExtension(path));
            // Transform the url (works with relative path to parent folder "../")
            contents = pattern.Replace(contents, m =>
            {
                var relativeUrl = m.Groups[2].Value;
                var urlReplace = GetUrlReplace(bundleUrlPath, relativeUrl, server);
                return string.Format("url({0}{1}{0})", m.Groups[1].Value, urlReplace);
            });
            File.WriteAllText(server.MapPath(bundleFilePath), contents);
            bundlePaths.Add(bundleFilePath);
        }
        base.Include(bundlePaths.ToArray());
        return this;
    }


    private string GetUrlReplace(string bundleUrlPath, string relativeUrl, HttpServerUtility server)
    {
        // Return the absolute uri
        Uri baseUri = new Uri("http://dummy.org");
        var absoluteUrl = new Uri(new Uri(baseUri, bundleUrlPath), relativeUrl).AbsolutePath;
        var localPath = server.MapPath(absoluteUrl);
        if (IsEmbedEnabled && File.Exists(localPath))
        {
            var fi = new FileInfo(localPath);
            if (fi.Length < 0x4000)
            {
                // Embed the image in uri
                string contentType = GetContentType(fi.Extension);
                if (null != contentType)
                {
                    var base64 = Convert.ToBase64String(File.ReadAllBytes(localPath));
                    // Return the serialized image
                    return string.Format("data:{0};base64,{1}", contentType, base64);
                }
            }
        }
        // Return the absolute uri 
        return absoluteUrl;
    }

Hope it helps, regards.

查看更多
笑指拈花
3楼-- · 2019-01-01 12:30

According to this thread on MVC4 css bundling and image references, if you define your bundle as:

bundles.Add(new StyleBundle("~/Content/css/jquery-ui/bundle")
                   .Include("~/Content/css/jquery-ui/*.css"));

Where you define the bundle on the same path as the source files that made up the bundle, the relative image paths will still work. The last part of the bundle path is really the file name for that specific bundle (i.e., /bundle can be any name you like).

This will only work if you are bundling together CSS from the same folder (which I think makes sense from a bundling perspective).

Update

As per the comment below by @Hao Kung, alternatively this may now be achieved by applying a CssRewriteUrlTransformation (Change relative URL references to CSS files when bundled).

NOTE: I have not confirmed comments regarding issues with rewriting to absolute paths within a virtual directory, so this may not work for everyone (?).

bundles.Add(new StyleBundle("~/Content/css/jquery-ui/bundle")
                   .Include("~/Content/css/jquery-ui/*.css",
                    new CssRewriteUrlTransform()));
查看更多
墨雨无痕
4楼-- · 2019-01-01 12:32

I found that CssRewriteUrlTransform fails to run if you're referencing a *.css file and you have the associated *.min.css file in the same folder.

To fix this, either delete the *.min.css file or reference it directly in your bundle:

bundles.Add(new Bundle("~/bundles/bootstrap")
    .Include("~/Libs/bootstrap3/css/bootstrap.min.css", new CssRewriteUrlTransform()));

After that you do that, your URLs will be transformed correctly and your images should be correctly resolved.

查看更多
登录 后发表回答