My site does not seem to use the cached JS/CSS files when the query string parameter changes.
When the user navigates the site, they go through a sequence of "pages"
www.myurl.com/Controller/Action/Id?Indx=1
www.myurl.com/Controller/Action/Id?Indx=3
www.myurl.com/Controller/Action/Id?Indx=4
www.myurl.com/Controller/Action/Id?Indx=2
www.myurl.com/Controller/Action/Id?Indx=6
Between each transition, there is a post to the Controller-Action.
I would like to be able to use the cached version of the LESS / CSS / JS files when the user hits the page after the first time (its always the same files).
However, it seems - because the Query string parameter (Indx) is different - the entire bundle is called from the server again & the CDN is also hit again.
This seems counter-productive. Is there a way out of this mess?
I am sending over my JS / LESS files as a minified bundle or I am using a CDN (for JQuery etc). For the minified bundles, in the cshtml file, I calling them using "Styles.Render"
Any insight would be great! Thanks!
Related
I have a website in IIS, under the website there are couples of web applications (website and web applications all are mvc web application).
The website uses ASP.NET MVC bundling -
public static void RegisterBundles(BundleCollection bundles)
{
bundles.Add(new StyleBundle("~/Content/css")
.IncludeDirectory("~/Content/Styles", "*.css", true));
bundles.Add(new ScriptBundle("~/Content/js")
.IncludeDirectory("~/Content/Scripts", "*.js", true));
}
In website _Layout.cshtml -
#Styles.Render("~/Content/css")
#Script.Render("~/Content/js")
When website is browsed (e.g. mywebsite.com), I can see querystring attached to those references.
<link href="/Content/css?v=asdsf$dfsdfdslkko" rel="stylesheet" />
<script src="/Content/js?v=jjkase$rads#asdasd"></script>
And for my child web application under that website in IIS, if I have to use same bundled resources, I can do (in webapp layout page) -
#Styles.Render("/Content/css")
#Script.Reder("/Content/js")
When webapp is browsed (e.g. mywebsite.com/mywebapp), I can access those resource from parent website but I couldnot get those querystrings in style and script references.
<link href="/Content/css" rel="stylesheet" />
<script src="/Content/js></scripts>
The only difference is I must use starting / for webapp which is the cause for not generating querystring. It's a weird behavior. I need querystring while browsing webapp too (to avoid cache). If I used / instead of ~ in my parent website also, querystring gets dissapeared (DISGUSTING).
Is there anyway to retain querystring even / is used for referencing bundles? Any help will be highly appreciated.
Thanks in advance.
First off, bundling is project-centric. There's no way to share bundles in one application with another, child or not. What you're doing here is essentially taking advantage of the fact that the bundling framework actually caches the created bundles to a real file, which then allows you to reference it in the other project as you would any other random static JavaScript/CSS file. However, with this approach, you're going to lose some of the niceties of using a bundle directly, such as the automatic appending of that cache-busting query string.
Technically, you can append your own query string. There's nothing special about it, really; it's just a random string that tricks the user's browser into thinking it's a different resource, and thus making it fetch it again. For example:
<script src="/Content/js?foo123></scripts>
However, you're going to be responsible for updating the query string whenever you change any of the scripts in that bundle. Using the actual bundle directly, it's smart enough to know something has changed and append a different query string, but if you're just statically linking, then there's no automated process to do that for you.
I need some help with Rejuicer. I just inherited a C# webapplication that uses it and I cannot figure out what exactly is going on.
in the global.asmx.cs under Application_Start we are
OnRequest.ForJs("~/Combined-{0}.js")
.Combine
.FilesIn("~/js-survey/")
.Matching("*.js")
.Configure();
I believe this is combining all the *.js in the js-survey/ folder into a single js called Combined-Uniquename.js.
Is this combining the file on the server?
Then in the Master Page it is using
"<%# Rejuiced.JsFor("~/Combined-{0}.js") %>"
is this retrieving the file from the server?
How can I test these?
s
Is this combining the file on the server?
It is dynamically combining the JavaScript files on app startup and storing them in memory. When a request is done to ~/Combined-HASH.js, Rejuicer handles the request and serves the combined files from memory. This is handled by RejuicerModule.
The {0} is replaced by a hash of the file. It ensures that when any of the files are changed, the cache is invalidated (since the "file name" will be different). Rejuiced.JsFor renders a <script> tag with the correct URL including the hash.
Take a look at the documentation and website (web.archive.org mirror, the site no longer exists) for more information.
Let's assume our app is offline, i.e. we can't use 3rd party CDNs thus we're creating our own.
I'd like to host all of the vendor scripts in a separate (Parent) web app and then include them in the bundles in several other MVC Apps.
e.g.
http://localhost/parentWeb/Scripts/jquery.js
http://localhost/parentWeb/Scripts/jquery-ui.js
http://localhost/parentWeb/Scripts/globalize.js
I'd like to include in the ASP.NET MVC App Website located in: http://localhost/parentWeb/childWeb
i.e. do something like this:
bundles.UseCdn = true;
bundles.Add(
new ScriptBundle(
"~/bundles/VendorScripts",
"http://localhost/parentWeb/Scripts/jquery.js",
"http://localhost/parentWeb/Scripts/jquery-ui.js",
"http://localhost/parentWeb/Scripts/globalize.js"));
...which of course isn't currently possible. Is there a good workaround?
You can't bundle external resources. If you think about it, it makes sense why you can't. It would require the bundler to actually download the resource and save it to the filesystem before it could work with it, and of course do it all asynchronously with some sort of fallback if the external resource couldn't be reached. And, then, it would have to do this on every page load because it can't check for lastmod (and therefore, know whether it actually needs to rebundle or not) without fetching the resource first.
If you use a CDN resource, the bundler merely prints the URL directly to the page; it doesn't make any modifications. Even then, it only lets you create a "bundle" of just that one URL, because 1) it wouldn't make sense to bundle multiple CDN resources since that would defeat the purpose of a CDN and 2) the bundle only exists in this scenario to provide a fallback if the CDN resource is unavailable. Otherwise, you would be served just as well by just hardcoding it to the page and not worrying about setting up a bundle at all.
I know this is an old topic, but I came here looking for an actual way to bundle CDN resources. From #Chris Pratt's answer, I understood it wasn't possible.
If you're wondering, I am working on optimizing an an existing project according to Google's Web Performance Best Practises which gives a low score when there are multiple script tags and a higher one when all scripts are bundled into a single script reference.
I needed a way to bundle all the CDN script resources as well as local resources in order. I worked on this github repo, which solved my problem.
With it, you build a bundle with a list of bundles, each containing a reference to the cdn resource, local resource to save to, and a Boolean indicating whether or not you want the bundle to be minified.
List<Bundle> jsBundles = new List<Bundle>();
jsBundles.Add(new Bundle("https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.0/jquery.min.js", #"~/jquery.min.js", Bundle.BundleType.JavaScript, false));
jsBundles.Add(new Bundle("https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.0/jquery-ui.min.js", #"~/jquery-ui.min.js", Bundle.BundleType.JavaScript, false));
jsBundles.Add(new Bundle(#"~/my-local-script.js", Bundle.BundleType.JavaScript, true));
To place on the page, you use
#jsBundles.Load();
This will process all bundles in the list, downloading content for bundles that have not been downloaded in the last 24 hours (It updates every 24 hours or when the web application restarts). All content downloaded will be placed in local files (where specified).
All content will be combined into the final result which will be spooled into the page in a script tag (or link tag for CSS).
The Load function also accepts a local File URL for the final script/css content. If specified, a tag with a src to the relative path for that local file will be given instead. E.g.
#jsBundles.Load("~/js/all-my-scripts.js");
The above statement will return something like:
<script src="~/js/all-my-scripts.js"></script>
An async attribute may be added to the script tag if the second parameter of the Load function is provided.
It also works on css cdn resources too. E.g.
List<Bundle> cssBundles = new List<Bundle>();
cssBundles.Add(new Bundle("https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.0/jquery-ui.min.css", #"~/jquery.ui.css", Bundle.BundleType.CSS, false));
cssBundles.Add(new Bundle(#"~/css/my-local-style.css", Bundle.BundleType.CSS, true));
#cssBundles.Load("~/css/all-my-styles.css");
This is for the benefit of those, who like me, came in here looking for a way to actually bundle CDN resources.
I have found a solution, which has nothing to do with CDN. Basically, granted the childWeb is hosted in the parentWeb's subdirectory, the following bundle configuration in the childWeb apps picks the file from the parentWeb and bundles them as usual:
bundles.Add(
new ScriptBundle(
"~/bundles/VendorScripts").Include(
"~/../Scripts/jquery.js",
"~/../Scripts/Scripts/jquery-ui.js",
"~/../Scripts/globalize.js"));
the important bit being: ~/../, which takes you one level up from the root location.
To use ScriptBundles with CDN resources, you need to use the overloaded constructor. Unfortunately you need to specify multiple ScriptBundles per file.
Here's a great blog post explaining things:
http://www.hanselman.com/blog/CDNsFailButYourScriptsDontHaveToFallbackFromCDNToLocalJQuery.aspx
And here's a code snippet:
bundles.UseCdn = true;
var bundle = new ScriptBundle("~/bundles/bundleNameHere", "//cdn.host.path/to/file");
// Path to the version of the file on your server (in case the CDN fails)
bundle.Include("~/../Scripts/path/to/file");
// JS expression to run, to test if CDN delivered the file or not
bundle.CdnFallbackExpression = "window.ValueYouExpectToBeTruthy";
bundles.Add(bundle);
The purpose of bundle is reduce the traffic of your web server where your web application hosted. using bundle the script files will not load in parallel. this is needed when you are using your local files.
but in case of cdn the files will load from cdn server so you don't have need to make bundle for cdn. you need bundle for local files only
When the bundle is registered in MVC4, what is responsible for "intercepting" incoming http requests for /bundles/someBundle?v=1hDzBpmYJm4Iu-OjRN1YqS1WeNThVl0kStLJGP8WCr41?
also since hash for each bundle is calculated only once (at a first request), where is that actually held, - and is it possible to return 404 if an incoming hash does not match
what is responsible for "intercepting" incoming http requests for ~/bundles/someBundle
There are no incoming requests to ~/bundles/someBundle. It's the server side helper that you are using (Scripts.Render) that on the server (within the same HTTP requests) interprets this value and spits the correct url in the resulting HTML.
also since hash for each bundle is calculated only once (at a first request), where is that actually held,
The actual bundle contents is stored in the server side cache : HttpContext.Cache. The actual hash represents a SHA256 hash on this content that is calculated every time you use the Scripts.Render helper.
UPDATE:
It's the System.Web.Optimization.BundleModule that is auto-registered when you reference the System.Web.Optimization assembly that is responsible for intercepting requests to urls like /bundles/someBundle?v=1hDzBpmYJm4Iu-OjRN1YqS1WeNThVl0kStLJGP8WCr41 and returning the actual contents.
You should have a file called BundleConfig.cs in a folder App_Start in your web project.
That section basically links a url "/bundles/something" to some script(s). When in accessing the site in Release mode (not debug activated), it will automatically merge the script into one in-memory file, minimize the script, add caching headers to the request and generate a hash of the file content.
If you are in debug, all the scripts should be separated to make debugging easier.
You either redefine the bundles you see in that file or declare some of your own.
Enjoy.
The reason to append a query string with a parameter based on contents of actual files that you are serving is solving the caching problem. Sou you can inform browsers to cache this requests for a long period of time and speed up subsequent pages load times.
So for developers of this bundling mechanism there is no difference what that parameter is. Only thing that is important is that if you change contents of your scripts or css - hash would change and it will force clients browser to request new files from the server.
As for what is responsible for intersepting this requests - there is source code of MVC available on codeplex, but i guess it plugs right into routing.
I have an ASP.NET Controller, that has a function, that simply returns a ContentResult of a WebClient.
In this case, CNN is my testbed, my WebClient, downloads CNN as a string, into it's content, and returns the result as an ActionResult/ContentResult on a controller.
I'm calling a RenderPartial on this action, to get CNN to display "below" and to the right of my current content, in its own box.
The problem I'm running into, is when you click a link on CNN, it redirects to a "relative" url sometimes, and that relative url, doesn't exist in my localhost, and won't exist on my web server, thus the link fails.
When it does redirect to an absolute url, that's also a problem, as the resulting page "replaces" my .NET shell.
What I need it to do, is take any URLs that are inside that ContentResult I returned, and if any of those URLs are clicked, pass them back to my .NET application, to be downloaded by the WebClient, and rendered back in that shell.
I'm aware I could use an IFRAME to do this instead of WebClient, but an IFRAME is impossible in my case, as its restricted by the public API we're consuming, its restricted/has flaws in the framework that API is servicing, and its blocked on some of our client machines, which we have no control over.
Also, the client machines will be off our network, so I can't use an AJAX load, as that'd be XSS.
One idea that I can think of, is basically to create custom routes, with filters/rules, that look for "CNN" like links, and pass them to my controller, as a parameter; then have my controller render my page, and pass those links to the web client.
This would obviously be a lot of work, and not even really sure where to start with it in the routing engine.
The only other thing I can think of that might work, is checking the validity of the URL, by attempting to see if it points to a valid link. If it doesn't point to a valid link, adding CNN's url prefix to it, and seeing if that is then a valid link. But that seems like a lot of work, for a solution that would be a heavy hit on performance, as checking each link on a page like CNN, could be very costly. Also, since I'd be running each link twice, that'd be a problem where POST operations were required, as it'd essentially run each operation twice, and being that this is a public API I'm consuming off a public framework, I wouldn't have control of the source code to safeguard against that.
Any other suggestions?
Is there any way to accomplish what I'm trying to do easily?