/// <summary> /// This will never return null, it will throw an exception for a null or empty relativePath - it is up to the particular implementation whether or not to throw /// an exception for invalid / inaccessible filenames (if no exception is thrown, the issue should be logged). It is up the the implementation to handle mapping /// the relative path to a full file path. /// </summary> public TextFileContents Load(string relativePath) { if (string.IsNullOrWhiteSpace(relativePath)) { throw new ArgumentException("Null/blank relativePath specified"); } var file = new FileInfo(_relativePathMapper.MapPath(relativePath)); if (!file.Exists) { throw new ArgumentException("Requested file does not exist: " + relativePath); } try { var fileContents = new TextFileContents( relativePath, file.LastWriteTime, File.ReadAllText(file.FullName) ); return(fileContents); } catch (Exception e) { throw new ArgumentException("Unable to load requested file: " + relativePath, e); } }
/// <summary> /// This will raise an exception if unable to determine the last modified date or if a null or empty relativePath is specified /// </summary> public DateTime GetLastModifiedDate(string relativePath) { if (string.IsNullOrWhiteSpace(relativePath)) { throw new ArgumentException("Null/blank relativePath specified"); } // 2016-10-27 DWR: It doesn't actually matter if this file exists, we can still access its Directory and then look for the most recent last-modified // date (note checking for this particular file has a benefit, it will allow us to create pseudo files such as a load-all-stylesheets-in-this-folder // relativePath that doesn't relate to a physical file) var file = new FileInfo(_relativePathMapper.MapPath(relativePath)); return(_extensionRestrictions .SelectMany(extension => file.Directory.GetFiles("*." + extension)) .Max(f => f.LastWriteTime)); }
/// <summary> /// This will raise an exception if unable to determine the last modified date or if a null or empty relativePath is specified /// </summary> public DateTime GetLastModifiedDate(string relativePath) { if (string.IsNullOrWhiteSpace(relativePath)) { throw new ArgumentException("Null/blank relativePath specified"); } var file = new FileInfo( _relativePathMapper.MapPath(relativePath) ); if (!file.Exists) { throw new ArgumentException("Invalid relativePath - file does not exist: " + relativePath); } return(file.LastWriteTime); }
/// <summary> /// This will combine a stylesheet with all of its imports (and any imports within those, and within those, etc..) and minify the resulting content for cases only /// where all files are in the same folder and no relative or absolute paths are specified in the import declarations. It incorporates caching of the minified /// content and implements 304 responses for cases where the request came with an If-Modified-Since header indicating that current content already exists on the /// client. The last-modified-date for the content is determined by retrieving the most recent LastWriteTime for any file in the folder - although this may lead /// to some false-positives if unrelated files are updated, it does mean that if any file that IS part of the combined stylesheet is updated then the content /// will be identified as stale and re-generated. The cached content will likewise be invalidated and updated if any files in the folder have changed since the /// date recorded for the cached data. GZip and Deflate compression of the response are supported where specified in Accept-Encoding request headers. /// </summary> private ActionResult Process( string relativePath, IRelativePathMapper relativePathMapper, ICacheThingsWithModifiedDates <TextFileContents> memoryCache, DateTime?lastModifiedDateFromRequest) { if (string.IsNullOrWhiteSpace(relativePath)) { throw new ArgumentException("Null/blank relativePath specified"); } if (memoryCache == null) { throw new ArgumentNullException(nameof(memoryCache)); } if (relativePathMapper == null) { throw new ArgumentNullException(nameof(relativePathMapper)); } // Using the SingleFolderLastModifiedDateRetriever means that we can determine whether cached content (either in the ASP.Net cache or in the browser cache) // is up to date without having to perform the complete import flattening process. It may lead to some unnecessary work if an unrelated file in the folder // is updated but for the most common cases it should be an efficient approach. var lastModifiedDateRetriever = new SingleFolderLastModifiedDateRetriever(relativePathMapper, new[] { "css", "less" }); var lastModifiedDate = lastModifiedDateRetriever.GetLastModifiedDate(relativePath); if ((lastModifiedDateFromRequest != null) && AreDatesApproximatelyEqual(lastModifiedDateFromRequest.Value, lastModifiedDate)) { Response.StatusCode = 304; return(Content("Not changed since last request", "text/css")); } var cssLoader = (new EnhancedNonCachedLessCssLoaderFactory( relativePathMapper, SourceMappingMarkerInjectionOptions.Inject, ErrorBehaviourOptions.LogAndRaiseException, new CSSMinifier.Logging.NullLogger() )).Get(); // Ignore any errors from the DiskCachingTextFileLoader - if the file contents become invalid then allow them to be deleted and rebuilt instead of blowing // up. The EnhancedNonCachedLessCssLoaderFactory should raise exceptionns since it will indicate invalid source content, which should be flagged up. var modifiedDateCachingStyleLoader = new CachingTextFileLoader( new DiskCachingTextFileLoader( cssLoader, relativePathRequested => new FileInfo(relativePathMapper.MapPath(relativePathRequested) + ".cache"), lastModifiedDateRetriever, DiskCachingTextFileLoader.InvalidContentBehaviourOptions.Delete, ErrorBehaviourOptions.LogAndContinue, new CSSMinifier.Logging.NullLogger() ), lastModifiedDateRetriever, memoryCache ); var content = modifiedDateCachingStyleLoader.Load(relativePath); if (content == null) { throw new Exception("Received null response from Css Loader - this should not happen"); } if ((lastModifiedDateFromRequest != null) && AreDatesApproximatelyEqual(lastModifiedDateFromRequest.Value, lastModifiedDate)) { Response.StatusCode = 304; return(Content("Not changed since last request", "text/css")); } SetResponseCacheHeadersForSuccess(content.LastModified); return(Content(content.Content, "text/css")); }