My question is similar to this:
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?
Better yet (IMHO) implement a custom Bundle that fixes the image paths. I wrote one for my app.
...
To use it, do:
...instead of...
What it does is (when not in debug mode) looks for
url(<something>)
and replaces it withurl(<absolute\path\to\something>)
. I wrote the thing about 10 seconds ago so it might need a little tweaking. I've taken into account fully-qualified URLs and base64 DataURIs by making sure there's no colons (:) in the URL path. In our environment, images normally reside in the same folder as their css files, but I've tested it with both parent folders (url(../someFile.png)
) and child folders (url(someFolder/someFile.png
).Another option would be to use the IIS URL Rewrite module to map the virtual bundle image folder to the physical image folder. Below is an example of a rewrite rule from that you could use for a bundle called "~/bundles/yourpage/styles" - note the regex matches on alphanumeric characters as well as hyphens, underscores and periods, which are common in image file names.
This approach creates a little extra overhead, but allows you to have more control over your bundle names, and also reduces the number of bundles you may have to reference on one page. Of course, if you have to reference multiple 3rd party css files that contain relative image path references, you still can't get around creating multiple bundles.
After little investigation I concluded the followings: You have 2 options:
go with transformations. Very usefull package for this: https://bundletransformer.codeplex.com/ you need following transformation for every problematic bundle:
Advantages: of this solution, you can name your bundle whatever you want => you can combine css files into one bundle from different directories. Disadvantages: You need to transform every problematic bundle
It is not necessary to specify a transform or have crazy subdirectory paths. After much troubleshooting I isolated it to this "simple" rule (is it a bug?)...
If your bundle path does not start with relative root of the items being included, then the web application root will not be taken into account.
Sounds like more of a bug to me, but anyway that's how you fix it with the current .NET 4.51 version. Perhaps the other answers were necessary on older ASP.NET builds, can't say don't have time to retrospectively test all that.
To clarify, here is an example:
I have these files...
Then setup the bundle like...
And render it like...
And get the "behaviour" (bug), the CSS files themselves have the application root (e.g. "http://localhost:1234/MySite/Content/Site.css") but the CSS image within all start "/Content/Images/..." or "/Images/..." depending on whether I add the transform or not.
Even tried creating the "Bundles" folder to see if it was to do with the path existing or not, but that didn't change anything. The solution to the problem is really the requirement that the name of the bundle must start with the path root.
Meaning this example is fixed by registering and rendering the bundle path like..
So of course you could say this is RTFM, but I am quite sure me and others picked-up this "~/Bundles/..." path from the default template or somewhere in documentation at MSDN or ASP.NET web site, or just stumbled upon it because actually it's a quite logical name for a virtual path and makes sense to choose such virtual paths which do not conflict with real directories.
Anyway, that's the way it is. Microsoft see no bug. I don't agree with this, either it should work as expected or some exception should be thrown, or an additional override to adding the bundle path which opts to include the application root or not. I can't imagine why anyone would not want the application root included when there was one (normally unless you installed your web site with a DNS alias/default web site root). So actually that should be the default anyway.
Maybe I am biased, but I quite like my solution as it doesn't do any transforming, regex's etc and it's has the least amount of code :)
This works for a site hosted as a Virtual Directory in a IIS Web Site and as a root website on IIS
So I created an Implentation of
IItemTransform
encapsulated theCssRewriteUrlTransform
and usedVirtualPathUtility
to fix the path and call the existing code:Seems to work fine for me?
I had this problem with bundles having incorrect path's to images and
CssRewriteUrlTransform
not resolving relative parent paths..
correctly (there was also problem with external resources like webfonts). That's why I wrote this custom transform (appears to do all of the above correctly):Edit: I didn't realize it, but I used some custom extension methods in the code. The source code of those is:
Of course it should be possible to replace
String.StartsWith(char)
withString.StartsWith(string)
.