Use client cache for resource files (css/js/images) without hassle
I usually creates a virtual path (VirtualPathNativeProvider) that all my css, images and javascript files is placed in. One reason for this is that later the editors have access to these files with out having to access the server.
The problem comes of course when these files changes. If the current VPP folder have set
- <staticFile expirationTime="4.0:0:0" />
the web browser will not get these new files before they force a cache reset. One way around this “problem” is to not cache it on the client. But that have some downsides.
My solution is to create my own VPP file system based on the VirtualPathNativeProvider.
The whole concept is that I remove a part of the path that contains Changed_ like this
- Regex filter = new Regex("/Changed_[^/]*/");
- string FilterCacheKeyPath(string virtualPath)
- {
- virtualPath = filter.Replace(virtualPath, "/");
- return virtualPath;
- }
Then I replace all the methods to use the striped version of the path/filename
- public class FilSystemWithCacheKey : EPiServer.Web.Hosting.VirtualPathNativeProvider
- {
- public FilSystemWithCacheKey(string name, NameValueCollection configParameters)
- : base(name, configParameters)
- {
- }
- public override bool FileExists(string virtualPath)
- {
- return base.FileExists(FilterCacheKeyPath(virtualPath));
- }
- public override bool DirectoryExists(string virtualPath)
- {
- return base.DirectoryExists(FilterCacheKeyPath(virtualPath));
- }
- public override System.Web.Hosting.VirtualFile GetFile(string virtualPath)
- {
- return base.GetFile(FilterCacheKeyPath(virtualPath));
- }
- public override System.Web.Hosting.VirtualDirectory GetDirectory(string virtualPath)
- {
- return base.GetDirectory(FilterCacheKeyPath(virtualPath));
- }
- public override EPiServer.Security.AccessLevel QueryAccess(string virtualPath, System.Security.Principal.IPrincipal user)
- {
- return base.QueryAccess(FilterCacheKeyPath(virtualPath), user);
- }
- Regex filter = new Regex("/Changed_[^/]*/");
- string FilterCacheKeyPath(string virtualPath)
- {
- virtualPath = filter.Replace(virtualPath, "/");
- return virtualPath;
- }
Then the fun begins ![]()
I created a method that have path and filename as input
- %=FilSystemWithCacheKey.GetCachePath("/Framework/","styles/screen.css") %>
That will return a path with the last changed inside the first path.
- public static string GetCachePath(string path, string filename)
- {
- var newUrl = HttpContext.Current.Cache["VersionPath_" + path + filename] as string;
- if (newUrl == null)
- {
- VirtualPathHandler instance = VirtualPathHandler.Instance;
- var dir = instance.GetDirectory(path, false) as NativeDirectory;
- var dirs = new List<string>();
- dirs.Add((dir as NativeDirectory).LocalPath);
- newUrl = path + "Changed_" + LastAccessed((dir as NativeDirectory).LocalPath, dirs) + "/" + filename;
- HttpContext.Current.Cache.Insert("VersionPath_" + path + filename, newUrl, new CacheDependency(dirs.ToArray()));
- }
- return newUrl;
- }
- public static string LastAccessed(string path, List<string> paths)
- {
- Stack<DirectoryInfo> dirs = new Stack<DirectoryInfo>();
- FileInfo mostRecent = null;
- dirs.Push(new DirectoryInfo(path));
- while (dirs.Count > 0)
- {
- DirectoryInfo current = dirs.Pop();
- Array.ForEach(current.GetFiles(), delegate(FileInfo f)
- {
- if (mostRecent == null || mostRecent.LastWriteTime < f.LastWriteTime)
- mostRecent = f;
- });
- Array.ForEach(current.GetDirectories(), delegate(DirectoryInfo d)
- {
- paths.Add(d.FullName);
- dirs.Push(d);
- });
- }
- return mostRecent.LastWriteTime.ToString("ddMMyyyy_HHmmss"); ;
- }
I add this to the Cache and add dependencies to all sub directories to the path.
since all my css links to the images relative to the path all changed to the /Framework/ files will force a new path and therefore a new client cache.
Comments