Less handler always caches

Nov 3, 2013 at 9:50 PM
It caches any changes for 15 minutes in debug mode in development.
Simply changing the web.config of course causes it to regenerate the file.

Looking at the code for the base handler, the method GetProcessedAssetContent just checks the cache and returns it. This verifies the situation above is exactly what the code is doing.
https://bundletransformer.codeplex.com/SourceControl/latest#BundleTransformer.Core/HttpHandlers/AssetHandlerBase.cs

I guess this code assumes the cache will be cleared by the cache dependency setup - however they are not.

For me, I just want to turn off ALL caching if the site is in debug mode. Is that possible?
Coordinator
Nov 4, 2013 at 10:10 AM
Hello, Wayne!

Mechanism of caching in the debugging HTTP handlers works correctly and will not change in the future.

I recommend to read the comments in «.less handler watching only main .less file?» discussion.
Nov 4, 2013 at 1:01 PM
Well, I have read that. It talks about an 'assethandler' option in web.confg, which is not valid anymore?
I am sure your caching works, but there is something wrong with expiring the cache or something - because it DEFINITELY does not expire them when the MAIN less file changes for me.
Coordinator
Nov 4, 2013 at 1:20 PM
Describe in details your problem and provide a code of the main LESS file.
Nov 4, 2013 at 1:44 PM
Well, the main less file includes bootsrap and just regular css/less code.
When I change the main less file (just edit anything - change any css or less), the cache is not invalidated.
The browser is requesting a new copy of the stylesheet, but your cache code is returning the cached version of it - because for whatever reason the cache was not invalidated.

Since I am using bundles - and those bundles are setup to handle the browser caching and take care of all of that - why can't the less code just never cache? Basically the only time it would ever be asked to generate the less file is when the stylebundle is no longer cached. Seems we have two levels of caching here. It seems logical to me that I would want to just turn off the caching at your level and just let the bundle handle it?

Anyway, this may not happen in production (under regular IIS, etc), but it does happen in development. I will have an opportunity to test it in a staging environment later today - help determine if it related to iis/express or what.

Thanks.
Nov 4, 2013 at 3:12 PM
Seems like this is the same issue as has been talked about here:
I still haven't resolved this issue in my projects and have resorted to using alternative http handlers for less files in debug mode.

I've tried dotless (only supports LESS 1.3.1) and LessCoffee, and both their http handlers for LESS files work as they should in debug mode, the one in Bundle Transformer does not, for whatever reason. It may be due to BT or due to a conflict with the CMS I'm using it with. But then that doesn't explain why the dotless or LessCoffee handlers are working, and not BT's.
Coordinator
Nov 4, 2013 at 3:30 PM
Nettsentrisk, I would like to know what a CMS you are using?
Nov 4, 2013 at 3:33 PM
EPiServer.
Nov 4, 2013 at 3:35 PM
nettsentrisk,
Yes, I see the exact same issues as you are seeing. Swap to anything but the bundle handler and it works great.

I think we need a 'do not cache' option for the handler - as I never need it cached, because the bundle does caching....and when I am not using bundles (at debug) I do not want cache.
Coordinator
Nov 4, 2013 at 4:16 PM
Server-side caching need to speed up debugging. Suppose that you have in addition to LESS-files are registered a lot of TypeScript-files (or CoffeeScript-files). If no server-side caching, then changing of one file should makes a translations of all other files, that will take time.

It seems to me that the problem is not in server-side caching, but that CMS (in this case, EPiServer) itself makes caching. Apparently caching control is based on the Last-Modified HTTP header, which is not supported by Bundle Transformer (due to the virtual file system is only supported the ETag HTTP header).

You are a commercial users of CMS and you should report about this problem to authors.
Nov 4, 2013 at 4:21 PM
I have been using dotless for all this time and have caching off - worked great, no speed issues, etc. However, if you want to cache it for that purpose that is fine, but it must be removed from cache when file changes - and that is for sure not working. (in development mode, using iisexpress).

The problem is DEFINITELY server side caching. I have reviewed everything and the browser makes a call into the website - the website has it cached. I am watching the packets, seeing the response - and the response is old.

I am not sure how to help you figure out what is wrong - but trust me something is definitely not working. (it is likely due to the cache notification of changed files mapped with IISExpress, etc). I would for one appreciate if I could selectively just turn off the caching as an easy fix/workaround.
Nov 4, 2013 at 4:24 PM
In that case, how are Cassette.Aspnet, LessCoffee and dotless all working with their HTTP handlers for less files within this CMS?

The only reason I'm not switching to Cassette.Aspnet right now is due to the fact that they force you to have "cassette.axd" in the URLs for the bundles, but most importantly, because they're stuck with dotless which is stuck at LESS 1.3.1.

LessCoffee as a supplement to BundleTransformer works - except it doesn't do the data-uri function properly with site root-relative URLs.

And dotless, as mentioned, is stuck at LESS 1.3.1...

I'm looking at how Cassette.Aspnet does their http handler now. They do caching a bit differently, might be worth a look.
Nov 4, 2013 at 4:35 PM
Nettsentrisk,
Agree with you. Taritsyn is convinced that his caching works and properly expires the cache, but I cannot make it work either. If I swap it for something else, it all works fine.

(Yes, dotless is stuck and 1.3.1 and that is why I am moving!)
Nov 4, 2013 at 5:16 PM
With Cassette installed in the CMS, it returns the following to serve a new version of the file from its LESS HTTP handler:
  • Cache-Control: no-cache
  • Expires: -1
  • Pragma: no-cache
It serves no ETag with a 200 response, only with a 304.

Bundle Transformer's handler returns:
  • Cache-Control: public, must-revalidate
  • ETag: .......................[etag here]...................
  • Expires: current datetime minus 1 year
So in the least, these two handlers are treating caching differently, one of them works within the CMS, and the other does not. I might try to drop the Bundle Transformer project directly into the CMS project solution to see if I can figure out where it's going wrong so I can find a fix for this....
Nov 4, 2013 at 5:21 PM
Nettsentrisk,
It is not related to browser caching. Do this:


Grab the URL to the stylesheet.
Edit your web.config (just resave it)
Open a new browser and tell it to go to that URL....you will see 200 response and the stylesheet in question.
Now, edit the less file - just change a background color or something.

Open a new browser and tell it to go to that URL....you will see the unmodified stylesheet.
Open a whole different browse (IE/Chrome/Firefox), ....you will see the unmodified stylesheet.
Hold down shift when you refresh page to force browser request.......you will see the unmodified stylesheet.
Use fiddler to get the page........you will see the unmodified stylesheet.

Why? Because bundletransfer has cached the generation of the less file and returns that each time.
Now, edit your web.config (just resave it). This causes a dump of the server side asp.net cache.

Open a new browser and tell it to go to that URL....you will see the new modified stylesheet!!
Nov 4, 2013 at 5:42 PM
Yes, but it seems to work fine without this problem if you run the example MVC site included in the Bundle Transformer code. (Test this if you haven't already.) 

So why is that working, but not with our projects? It's odd. 

WayneBrantley <[email removed]> wrote:

From: WayneBrantley

Nettsentrisk,
It is not related to browser caching. Do this:


Grab the URL to the stylesheet.
Edit your web.config (just resave it)
Open a new browser and tell it to go to that URL....you will see 200 response and the stylesheet in question.
Now, edit the less file - just change a background color or something.

Open a new browser and tell it to go to that URL....you will see the unmodified stylesheet.
Open a whole different browse (IE/Chrome/Firefox), ....you will see the unmodified stylesheet.
Hold down shift when you refresh page to force browser request.......you will see the unmodified stylesheet.
Use fiddler to get the page........you will see the unmodified stylesheet.

Why? Because bundletransfer has cached the generation of the less file and returns that each time.
Now, edit your web.config (just resave it). This causes a dump of the server side asp.net cache.

Open a new browser and tell it to go to that URL....you will see the new modified stylesheet!!
Coordinator
Nov 4, 2013 at 7:50 PM
Wayne, describe your surroundings:
  1. Version number of .NET Framework
  2. Version number of ASP.NET MVC
  3. Version number and edition of IIS
  4. If you are using a CMS, then write its name
Coordinator
Nov 4, 2013 at 8:00 PM
George, I was used the HTTP headers like as Cassette:
Cache-Control: no-cache 
Expires: -1 
Pragma: no-cache
and they didn't work in your CMS.

Your CMS can override HostingEnvironment.VirtualPathProvider?
Nov 4, 2013 at 8:09 PM
.net 4.5.1
MVC 4
IIS Express on windows 7 x64.
No CMS

Like I said it all has to be around it not detecting the file change...
Nov 4, 2013 at 11:11 PM
I "fixed" the caching issue by removing each altered asset from the cache (_cache.Remove(cacheItemKey)), and commenting out the code where they are inserted into the cache. Now altered files are served with the new version after each alteration of the file. I assume the dependencies don't work anymore, though. So if you change another file a file depends on, it won't update itself.

Anyways, I'm guessing there's a problem with using the System Web Cache, with the cache dependencies, and virtual file system, for some reason. It seems to be overriding the response being sent out by the http handler.

Could this be fixed by doing what Cassette.Aspnet does, add the etag on to the end of the URL of the asset file name as a query string?

Yes, Taritsyn, in the EPiServer config file, you can set up VirtualPathProviders. Is there something I should do in regards to this? Set BundleTable.VirtualPathProvider = HostingEnvironment.VirtualPathProvider? Or something else?
Coordinator
Nov 5, 2013 at 7:27 AM
Nettsentrisk wrote:
Could this be fixed by doing what Cassette.Aspnet does, add the etag on to the end of the URL of the asset file name as a query string?
I can't implement it because the script and style links generated at level of the Microsoft ASP.NET Web Optimization Framework.

You can vote for the feature request «Add support for versioning files in debug mode automatically».
Nov 5, 2013 at 8:01 AM
Ah, yes, correct.

From what I've found out by playing with the BT code within my own project now is that an asset is placed into the cache in the GetProcessedAssetContent method, after which the same asset will always be taken out from the cache and never from a new version. There's nothing in place that invalidates the item in the cache, other than the file dependencies, but these don't seem to be working properly, at least within my project.

In other words, I can get the BT http less handler to work properly by just bypassing the backend cache entirely. This will be a slight performance hit since 200 requests will always be served straight from the file system, but 304 requests will still just return nothing. In debug mode, though, this doesn't matter much.

But I'm puzzled as to why the caching part in GetProcessedAssetContent is not working.
Coordinator
Nov 5, 2013 at 8:19 AM
To George and Wayne:

GetProcessedAssetContent method working correctly, but provided that the BundleTable.VirtualPathProvider.GetCacheDependency method also works correctly.

I need to know what is value will return the following code in your project:
string vppFullTypeName = BundleTable.VirtualPathProvider.GetType().FullName;
I have this value equals to System.Web.Hosting.MapPathBasedVirtualPathProvider.
Nov 5, 2013 at 9:10 AM
vppFullTypeName = "EPiServer.Web.Hosting.VirtualPathNonUnifiedProvider"
Coordinator
Nov 5, 2013 at 9:40 AM
Nettsentrisk wrote:
vppFullTypeName = "EPiServer.Web.Hosting.VirtualPathNonUnifiedProvider"
It seems that in the EPiServer.Web.Hosting.VirtualPathnonunifiedprovider incorrectly implemented the GetCacheDependency method.

Waiting for an answer from Wayne.
Nov 5, 2013 at 9:54 AM
So if I set, in Global.asax, BundleTable.VirtualPathProvider = System.Web.Hosting.MapPathBasedVirtualPathProvider, it should work properly?
Nov 5, 2013 at 10:29 AM
Edited Nov 5, 2013 at 10:30 AM
Think I've found the whole source of the problem now. The GetCacheDependency method is returning "null", thus every asset is being inserted into the cache without a cache dependency, leading to it never being invalidated!

Here's how EPiServer has implemented this method:
public override CacheDependency GetCacheDependency(string virtualPath, IEnumerable virtualPathDependencies, DateTime utcStart)
{
  return IsEmbeddedResourcePath(virtualPath) ?
      null :
      base.GetCacheDependency(virtualPath, virtualPathDependencies, utcStart);
}
So I'm guessing IsEmbeddedResoucePath is returning true for the asset paths, thereby returning null for the cache dependency. How can I fix this??
Nov 5, 2013 at 12:09 PM
Actually the code I posted earlier is incorrect, this is the actual override that's causing the problem:
public override CacheDependency GetCacheDependency(string virtualPath, IEnumerable virtualPathDependencies, DateTime utcStart)
{
    string str;
    if (!this.MapVirtualPathToLocalPath(virtualPath, out str) && (base.Previous != null))
    {
        base.Previous.GetCacheDependency(virtualPath, virtualPathDependencies, utcStart);
    }
    IEnumerable<string> enumerable = virtualPathDependencies.OfType<string>();
    List<string> list = new List<string>();
    foreach (string str2 in enumerable)
    {
        NonUnifiedFile file = GenericHostingEnvironment.VirtualPathProvider.GetFile(str2) as NonUnifiedFile;
        if (file != null)
        {
            list.Add(file.PhysicalPath);
        }
    }
    if (list.Count == 0)
    {
        return null;
    }
    return new CacheDependency(list.ToArray(), null, utcStart);
}
Coordinator
Nov 5, 2013 at 1:00 PM
I think it is right:
    ...
    if (!this.MapVirtualPathToLocalPath(virtualPath, out str) && (base.Previous != null))
    {
        return base.Previous.GetCacheDependency(virtualPath, virtualPathDependencies, utcStart);
    }
    ...
Nov 5, 2013 at 1:18 PM
Well then, that appears to be a bug in EPiServer then. We'll see if we can work around it, and will probably report the bug in to them as well.

That won't help Wayne, though :)

Thanks for the help so far, hopefully we'll be able to use Bundle Transformer on its own shortly!
Nov 5, 2013 at 1:27 PM
This: string vppFullTypeName = BundleTable.VirtualPathProvider.GetType().FullName;

in my environment is equal to:
"System.Web.Hosting.MapPathBasedVirtualPathProvider"
Coordinator
Nov 5, 2013 at 1:53 PM
To Wayne:

Then let's clarify the following:
  1. What version of ASP.NET specified in the Web.config file (<compilation debug="false" targetFramework="VERSION_NUMBER" />)?
  2. What version number of IIS Express?
  3. What version number of Visual Studio?
  4. The error occurs when using Browser Link?
Nov 5, 2013 at 1:56 PM
version 4.5
IIS Express - 8.0.8418
VS2012

By browser link - means if I just pull up the link the the stylesheet via browser, then yes. (And it is not an error - it is just the cached resource)
Coordinator
Nov 5, 2013 at 4:47 PM
To Wayne:

While I can't reproduce this bug on similar configuration :-(
Nov 5, 2013 at 11:49 PM
Could I please get an option to just disable caching?
Coordinator
Nov 6, 2013 at 6:20 AM
I will add this option in the next release.
Nov 6, 2013 at 8:12 AM
Great, that would allow me to circumvent the bug in EPiServer until it's fixed :)
Nov 6, 2013 at 12:35 PM
Awesome. Really appreciate your time.
Coordinator
Nov 7, 2013 at 6:39 PM
In Bundle Transformer 1.8.11 you can now disable server-side and client-side cache of the debugging HTTP handlers:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <bundleTransformer xmlns="http://tempuri.org/BundleTransformer.Configuration.xsd">
    <core>
      …
      <assetHandler disableServerCache="true" disableClientCache="true" />
      …
    </core>
    …
  </bundleTransformer>
</configuration>
Nov 8, 2013 at 10:15 AM
I've tested 1.8.11 with disableServerCache - and it works like a charm! Wonderful that you've added this feature!