diff --git a/src/ImageProcessor.Web.AzureBlobCache/AzureBlobCache.cs b/src/ImageProcessor.Web.AzureBlobCache/AzureBlobCache.cs index d6beca980..3820f421a 100644 --- a/src/ImageProcessor.Web.AzureBlobCache/AzureBlobCache.cs +++ b/src/ImageProcessor.Web.AzureBlobCache/AzureBlobCache.cs @@ -22,7 +22,7 @@ /// /// The max age. /// - private readonly int maxAge; + private readonly int maxDays; private CloudStorageAccount cloudCachedStorageAccount; @@ -46,10 +46,7 @@ public AzureBlobCache(string requestPath, string fullPath, string querystring) : base(requestPath, fullPath, querystring) { - // TODO: Get from configuration. - this.Settings = new Dictionary(); - - this.maxAge = Convert.ToInt32(this.Settings["MaxAge"]); + this.maxDays = Convert.ToInt32(this.Settings["MaxAge"]); // Retrieve storage accounts from connection string. this.cloudCachedStorageAccount = CloudStorageAccount.Parse(this.Settings["CachedStorageAccount"]); @@ -66,11 +63,11 @@ this.cachedContainerRoot = this.Settings["CachedContainerRoot"]; } - public override int MaxAge + public override int MaxDays { get { - return this.maxAge; + return this.maxDays; } } @@ -83,24 +80,44 @@ string pathFromKey = string.Join("\\", cachedFileName.ToCharArray().Take(6)); this.CachedPath = Path.Combine(this.cachedContainerRoot, pathFromKey, cachedFileName).Replace(@"\", "/"); - ICloudBlob blockBlob = await this.cloudCachedBlobContainer - .GetBlobReferenceFromServerAsync(this.RequestPath); - bool isUpdated = false; - if (!await blockBlob.ExistsAsync()) - { - // Nothing in the cache so we should return true. - isUpdated = true; - } - else + CachedImage cachedImage = CacheIndexer.GetValue(this.CachedPath); + + if (cachedImage == null) { - // Pull the latest info. - await blockBlob.FetchAttributesAsync(); - if (blockBlob.Properties.LastModified.HasValue) + ICloudBlob blockBlob = + await this.cloudCachedBlobContainer.GetBlobReferenceFromServerAsync(this.RequestPath); + + if (await blockBlob.ExistsAsync()) + { + // Pull the latest info. + await blockBlob.FetchAttributesAsync(); + + if (blockBlob.Properties.LastModified.HasValue) + { + cachedImage = new CachedImage + { + Key = Path.GetFileNameWithoutExtension(this.CachedPath), + Path = this.CachedPath, + CreationTimeUtc = + blockBlob.Properties.LastModified.Value.UtcDateTime + }; + + CacheIndexer.Add(cachedImage); + } + } + + if (cachedImage == null) + { + // Nothing in the cache so we should return true. + isUpdated = true; + } + else { // Check to see if the cached image is set to expire. - if (this.IsExpired(blockBlob.Properties.LastModified.Value.UtcDateTime)) + if (this.IsExpired(cachedImage.CreationTimeUtc)) { + CacheIndexer.Remove(this.CachedPath); isUpdated = true; } } @@ -141,8 +158,15 @@ .Cast() .OrderBy(b => b.Properties.LastModified != null ? b.Properties.LastModified.Value.UtcDateTime : new DateTime())) { - if (blob.Properties.LastModified.HasValue && !this.IsExpired(blob.Properties.LastModified.Value.UtcDateTime)) + if (blob.Properties.LastModified.HasValue + && !this.IsExpired(blob.Properties.LastModified.Value.UtcDateTime)) + { + break; + } + else { + // Remove from the cache and delete each CachedImage. + CacheIndexer.Remove(blob.Name); await blob.DeleteAsync(); } } diff --git a/src/ImageProcessor.Web/Caching/CacheIndexer.cs b/src/ImageProcessor.Web/Caching/CacheIndexer.cs index e69e65c59..8a7786eb3 100644 --- a/src/ImageProcessor.Web/Caching/CacheIndexer.cs +++ b/src/ImageProcessor.Web/Caching/CacheIndexer.cs @@ -15,9 +15,9 @@ namespace ImageProcessor.Web.Caching using System.Runtime.Caching; /// - /// Represents an in memory collection of keys and values whose operations are concurrent. + /// Represents an in memory collection of cached images whose operations are concurrent. /// - internal static class CacheIndexer + public static class CacheIndexer { #region Public /// @@ -34,30 +34,6 @@ namespace ImageProcessor.Web.Caching { string key = Path.GetFileNameWithoutExtension(cachedPath); CachedImage cachedImage = (CachedImage)MemCache.GetItem(key); - - if (cachedImage == null) - { - // FileInfo is thread safe. - FileInfo fileInfo = new FileInfo(cachedPath); - - if (!fileInfo.Exists) - { - return null; - } - - // Pull the latest info. - fileInfo.Refresh(); - - cachedImage = new CachedImage - { - Key = Path.GetFileNameWithoutExtension(cachedPath), - Path = cachedPath, - CreationTimeUtc = fileInfo.CreationTimeUtc - }; - - Add(cachedImage); - } - return cachedImage; } @@ -92,7 +68,7 @@ namespace ImageProcessor.Web.Caching CacheItemPolicy policy = new CacheItemPolicy(); policy.ChangeMonitors.Add(new HostFileChangeMonitor(new List { cachedImage.Path })); - MemCache.AddItem(cachedImage.Key, cachedImage, policy); + MemCache.AddItem(Path.GetFileNameWithoutExtension(cachedImage.Key), cachedImage, policy); return cachedImage; } #endregion diff --git a/src/ImageProcessor.Web/Caching/CachedImage.cs b/src/ImageProcessor.Web/Caching/CachedImage.cs index 511c2b318..25519fcc4 100644 --- a/src/ImageProcessor.Web/Caching/CachedImage.cs +++ b/src/ImageProcessor.Web/Caching/CachedImage.cs @@ -15,7 +15,7 @@ namespace ImageProcessor.Web.Caching /// /// Describes a cached image /// - internal sealed class CachedImage + public sealed class CachedImage { /// /// Gets or sets the key identifying the cached image. diff --git a/src/ImageProcessor.Web/Caching/DiskCache.cs b/src/ImageProcessor.Web/Caching/DiskCache.cs index fd0778e76..e0e2ec555 100644 --- a/src/ImageProcessor.Web/Caching/DiskCache.cs +++ b/src/ImageProcessor.Web/Caching/DiskCache.cs @@ -1,39 +1,19 @@ -// -------------------------------------------------------------------------------------------------------------------- -// -// Copyright (c) James South. -// Licensed under the Apache License, Version 2.0. -// -// -// The disk cache. -// -// -------------------------------------------------------------------------------------------------------------------- - -namespace ImageProcessor.Web.Caching +namespace ImageProcessor.Web.Caching { - #region Using using System; using System.Collections.Generic; - using System.Globalization; + using System.Configuration; using System.IO; using System.Linq; + using System.Threading.Tasks; + using System.Web; using System.Web.Hosting; using ImageProcessor.Web.Configuration; using ImageProcessor.Web.Extensions; - using ImageProcessor.Web.Helpers; - #endregion - /// - /// The disk cache. - /// - internal sealed class DiskCache + public class DiskCache : ImageCacheBase { - #region Fields - /// - /// The maximum number of days to cache files on the system for. - /// - internal static readonly int MaxFileCachedDuration = ImageProcessorConfiguration.Instance.MaxCacheDays; - /// /// The maximum number of files allowed in the directory. /// @@ -47,42 +27,25 @@ namespace ImageProcessor.Web.Caching private const int MaxFilesCount = 100; /// - /// The virtual cache path. - /// - private static readonly string VirtualCachePath = ImageProcessorConfiguration.Instance.VirtualCachePath; - - /// - /// The absolute path to virtual cache path on the server. - /// - private static readonly string AbsoluteCachePath = HostingEnvironment.MapPath(ImageProcessorConfiguration.Instance.VirtualCachePath); - - /// - /// The request path for the image. - /// - private readonly string requestPath; - - /// - /// The full path for the image. + /// The max age. /// - private readonly string fullPath; + private readonly int maxDays; /// - /// The querystring containing processing instructions. + /// The virtual cache path. /// - private readonly string querystring; + private readonly string virtualCachePath; /// - /// The physical cached path. + /// The absolute path to virtual cache path on the server. /// - private string physicalCachedPath; + private readonly string absoluteCachePath; /// - /// The virtual cached path. + /// The virtual cached path to the cached file. /// - private string virtualCachedPath; - #endregion + private string virtualCachedFilePath; - #region Constructors /// /// Initializes a new instance of the class. /// @@ -96,49 +59,103 @@ namespace ImageProcessor.Web.Caching /// The querystring containing instructions. /// public DiskCache(string requestPath, string fullPath, string querystring) + : base(requestPath, fullPath, querystring) { - this.requestPath = requestPath; - this.fullPath = fullPath; - this.querystring = querystring; + this.maxDays = Convert.ToInt32(this.Settings["MaxAge"]); + string virtualPath = this.Settings["VirtualCachePath"]; + + if (!virtualPath.IsValidVirtualPathName()) + { + throw new ConfigurationErrorsException("DiskCache 'VirtualCachePath' is not a valid virtual path."); + } - // Get the physical and virtual paths. - this.GetCachePaths(); + this.virtualCachePath = virtualPath; + + this.absoluteCachePath = HostingEnvironment.MapPath(this.virtualCachePath); } - #endregion /// - /// Gets the cached path. + /// The maximum number of days to cache files on the system for. + /// TODO: Shift the getter source to proper config. /// - public string CachedPath + public override int MaxDays { get { - return this.physicalCachedPath; + return this.maxDays; } } - /// - /// Gets the cached path. - /// - public string VirtualCachedPath + public override async Task IsNewOrUpdatedAsync() { - get + string cachedFileName = await this.CreateCachedFileName(); + + // Collision rate of about 1 in 10000 for the folder structure. + // That gives us massive scope to store millions of files. + string pathFromKey = string.Join("\\", cachedFileName.ToCharArray().Take(6)); + string virtualPathFromKey = pathFromKey.Replace(@"\", "/"); + this.CachedPath = Path.Combine(this.absoluteCachePath, pathFromKey, cachedFileName); + this.virtualCachedFilePath = Path.Combine(this.virtualCachePath, virtualPathFromKey, cachedFileName).Replace(@"\", "/"); + + bool isUpdated = false; + CachedImage cachedImage = CacheIndexer.GetValue(this.CachedPath); + + if (cachedImage == null) + { + FileInfo fileInfo = new FileInfo(this.CachedPath); + + if (fileInfo.Exists) + { + // Pull the latest info. + fileInfo.Refresh(); + + cachedImage = new CachedImage + { + Key = Path.GetFileNameWithoutExtension(this.CachedPath), + Path = this.CachedPath, + CreationTimeUtc = fileInfo.CreationTimeUtc + }; + + CacheIndexer.Add(cachedImage); + } + } + + if (cachedImage == null) + { + // Nothing in the cache so we should return true. + isUpdated = true; + } + else { - return this.virtualCachedPath; + // Check to see if the cached image is set to expire. + if (this.IsExpired(cachedImage.CreationTimeUtc)) + { + CacheIndexer.Remove(this.CachedPath); + isUpdated = true; + } } + + return isUpdated; } - #region Methods - #region Public - /// - /// Trims a cached folder ensuring that it does not exceed the maximum file count. - /// - /// - /// The path to the folder. - /// - public static void TrimCachedFolders(string path) + public override async Task AddImageToCacheAsync(Stream stream) { - string directory = Path.GetDirectoryName(path); + // ReSharper disable once AssignNullToNotNullAttribute + DirectoryInfo directoryInfo = new DirectoryInfo(Path.GetDirectoryName(this.CachedPath)); + if (!directoryInfo.Exists) + { + directoryInfo.Create(); + } + + using (FileStream fileStream = File.Create(this.CachedPath)) + { + await stream.CopyToAsync(fileStream); + } + } + + public override async Task TrimCacheAsync() + { + string directory = Path.GetDirectoryName(this.CachedPath); if (directory != null) { @@ -148,7 +165,7 @@ namespace ImageProcessor.Web.Caching if (parentDirectoryInfo != null) { // UNC folders can throw exceptions if the file doesn't exist. - foreach (DirectoryInfo enumerateDirectory in parentDirectoryInfo.SafeEnumerateDirectories()) + foreach (DirectoryInfo enumerateDirectory in await parentDirectoryInfo.SafeEnumerateDirectoriesAsync()) { IEnumerable files = enumerateDirectory.EnumerateFiles().OrderBy(f => f.CreationTimeUtc); int count = files.Count(); @@ -160,7 +177,7 @@ namespace ImageProcessor.Web.Caching // If the group count is equal to the max count minus 1 then we know we // have reduced the number of items below the maximum allowed. // We'll cleanup any orphaned expired files though. - if (!IsExpired(fileInfo.CreationTimeUtc) && count <= MaxFilesCount - 1) + if (!this.IsExpired(fileInfo.CreationTimeUtc) && count <= MaxFilesCount - 1) { break; } @@ -181,130 +198,10 @@ namespace ImageProcessor.Web.Caching } } - /// - /// Adds an image to the cache. - /// - /// - /// The path to the cached image. - /// - public void AddImageToCache(string cachedPath) - { - string key = Path.GetFileNameWithoutExtension(cachedPath); - CachedImage cachedImage = new CachedImage - { - Key = key, - Path = cachedPath, - CreationTimeUtc = DateTime.UtcNow - }; - - CacheIndexer.Add(cachedImage); - } - - /// - /// Returns a value indicating whether the original file is new or has been updated. - /// - /// - /// The path to the cached image. - /// - /// - /// True if The original file is new or has been updated; otherwise, false. - /// - public bool IsNewOrUpdatedFile(string cachedPath) + public override void RewritePath(HttpContext context) { - bool isUpdated = false; - CachedImage cachedImage = CacheIndexer.GetValue(cachedPath); - - if (cachedImage == null) - { - // Nothing in the cache so we should return true. - isUpdated = true; - } - else - { - // Check to see if the cached image is set to expire. - if (IsExpired(cachedImage.CreationTimeUtc)) - { - CacheIndexer.Remove(cachedPath); - isUpdated = true; - } - } - - return isUpdated; - } - #endregion - - #region Private - /// - /// Gets a value indicating whether the given images creation date is out with - /// the prescribed limit. - /// - /// - /// The creation date. - /// - /// - /// The true if the date is out with the limit, otherwise; false. - /// - private static bool IsExpired(DateTime creationDate) - { - return creationDate.AddDays(MaxFileCachedDuration) < DateTime.UtcNow.AddDays(-MaxFileCachedDuration); - } - - /// - /// Gets the full transformed cached paths for the image. - /// The images are stored in paths that are based upon the SHA1 of their full request path - /// taking the individual characters of the hash to determine their location. - /// This allows us to store millions of images. - /// - private void GetCachePaths() - { - string streamHash = string.Empty; - - if (AbsoluteCachePath != null) - { - try - { - if (new Uri(this.requestPath).IsFile) - { - // Get the hash for the filestream. That way we can ensure that if the image is - // updated but has the same name we will know. - FileInfo imageFileInfo = new FileInfo(this.requestPath); - if (imageFileInfo.Exists) - { - // Pull the latest info. - imageFileInfo.Refresh(); - - // Checking the stream itself is far too processor intensive so we make a best guess. - string creation = imageFileInfo.CreationTimeUtc.ToString(CultureInfo.InvariantCulture); - string length = imageFileInfo.Length.ToString(CultureInfo.InvariantCulture); - streamHash = string.Format("{0}{1}", creation, length); - } - } - } - catch - { - streamHash = string.Empty; - } - - // Use an sha1 hash of the full path including the querystring to create the image name. - // That name can also be used as a key for the cached image and we should be able to use - // The characters of that hash as sub-folders. - string parsedExtension = ImageHelpers.GetExtension(this.fullPath, this.querystring); - string encryptedName = (streamHash + this.fullPath).ToSHA1Fingerprint(); - - // Collision rate of about 1 in 10000 for the folder structure. - string pathFromKey = string.Join("\\", encryptedName.ToCharArray().Take(6)); - string virtualPathFromKey = pathFromKey.Replace(@"\", "/"); - - string cachedFileName = string.Format( - "{0}.{1}", - encryptedName, - !string.IsNullOrWhiteSpace(parsedExtension) ? parsedExtension.Replace(".", string.Empty) : "jpg"); - - this.physicalCachedPath = Path.Combine(AbsoluteCachePath, pathFromKey, cachedFileName); - this.virtualCachedPath = Path.Combine(VirtualCachePath, virtualPathFromKey, cachedFileName).Replace(@"\", "/"); - } + // The cached file is valid so just rewrite the path. + context.RewritePath(this.virtualCachedFilePath, false); } - #endregion - #endregion } } diff --git a/src/ImageProcessor.Web/Caching/DiskCache2.cs b/src/ImageProcessor.Web/Caching/DiskCache2.cs deleted file mode 100644 index f4e04e7c3..000000000 --- a/src/ImageProcessor.Web/Caching/DiskCache2.cs +++ /dev/null @@ -1,168 +0,0 @@ -namespace ImageProcessor.Web.Caching -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using System.Threading.Tasks; - using System.Web; - using System.Web.Hosting; - - using ImageProcessor.Web.Configuration; - using ImageProcessor.Web.Extensions; - - public class DiskCache2 : ImageCacheBase - { - /// - /// The maximum number of files allowed in the directory. - /// - /// - /// NTFS directories can handle up to 10,000 files in the directory before slowing down. - /// This will help us to ensure that don't go over that limit. - /// - /// - /// - /// - private const int MaxFilesCount = 100; - - /// - /// The max age. - /// - private readonly int maxAge; - - /// - /// The virtual cache path. - /// - private readonly string virtualCachePath; - - /// - /// The absolute path to virtual cache path on the server. - /// - private readonly string absoluteCachePath; - - /// - /// The virtual cached path to the cached file. - /// - private string virtualCachedFilePath; - - public DiskCache2(string requestPath, string fullPath, string querystring) - : base(requestPath, fullPath, querystring) - { - // TODO: Get from configuration. - this.Settings = new Dictionary(); - this.maxAge = Convert.ToInt32(this.Settings["MaxAge"]); - this.virtualCachePath = this.Settings["VirtualCachePath"]; - this.absoluteCachePath = HostingEnvironment.MapPath(this.virtualCachePath); - } - - /// - /// The maximum number of days to cache files on the system for. - /// TODO: Shift the getter source to proper config. - /// - public override int MaxAge - { - get - { - return this.maxAge; - } - } - - public override async Task IsNewOrUpdatedAsync() - { - string cachedFileName = await this.CreateCachedFileName(); - - // Collision rate of about 1 in 10000 for the folder structure. - // That gives us massive scope to store millions of files. - string pathFromKey = string.Join("\\", cachedFileName.ToCharArray().Take(6)); - string virtualPathFromKey = pathFromKey.Replace(@"\", "/"); - this.CachedPath = Path.Combine(this.absoluteCachePath, pathFromKey, cachedFileName); - this.virtualCachedFilePath = Path.Combine(this.virtualCachePath, virtualPathFromKey, cachedFileName).Replace(@"\", "/"); - - bool isUpdated = false; - CachedImage cachedImage = CacheIndexer.GetValue(this.CachedPath); - - if (cachedImage == null) - { - // Nothing in the cache so we should return true. - isUpdated = true; - } - else - { - // Check to see if the cached image is set to expire. - if (this.IsExpired(cachedImage.CreationTimeUtc)) - { - CacheIndexer.Remove(this.CachedPath); - isUpdated = true; - } - } - - return isUpdated; - } - - public override async Task AddImageToCacheAsync(Stream stream) - { - // ReSharper disable once AssignNullToNotNullAttribute - DirectoryInfo directoryInfo = new DirectoryInfo(Path.GetDirectoryName(this.CachedPath)); - if (!directoryInfo.Exists) - { - directoryInfo.Create(); - } - - using (FileStream fileStream = File.Create(this.CachedPath)) - { - await stream.CopyToAsync(fileStream); - } - } - - public override async Task TrimCacheAsync() - { - string directory = Path.GetDirectoryName(this.CachedPath); - - if (directory != null) - { - DirectoryInfo directoryInfo = new DirectoryInfo(directory); - DirectoryInfo parentDirectoryInfo = directoryInfo.Parent; - - if (parentDirectoryInfo != null) - { - // UNC folders can throw exceptions if the file doesn't exist. - foreach (DirectoryInfo enumerateDirectory in await parentDirectoryInfo.SafeEnumerateDirectoriesAsync()) - { - IEnumerable files = enumerateDirectory.EnumerateFiles().OrderBy(f => f.CreationTimeUtc); - int count = files.Count(); - - foreach (FileInfo fileInfo in files) - { - try - { - // If the group count is equal to the max count minus 1 then we know we - // have reduced the number of items below the maximum allowed. - // We'll cleanup any orphaned expired files though. - if (!this.IsExpired(fileInfo.CreationTimeUtc) && count <= MaxFilesCount - 1) - { - break; - } - - // Remove from the cache and delete each CachedImage. - CacheIndexer.Remove(fileInfo.Name); - fileInfo.Delete(); - count -= 1; - } - // ReSharper disable once EmptyGeneralCatchClause - catch - { - // Do nothing; skip to the next file. - } - } - } - } - } - } - - public override void RewritePath(HttpContext context) - { - // The cached file is valid so just rewrite the path. - context.RewritePath(this.virtualCachedFilePath, false); - } - } -} diff --git a/src/ImageProcessor.Web/Caching/IImageCache.cs b/src/ImageProcessor.Web/Caching/IImageCache.cs index d6404ff51..3b2d1d337 100644 --- a/src/ImageProcessor.Web/Caching/IImageCache.cs +++ b/src/ImageProcessor.Web/Caching/IImageCache.cs @@ -11,11 +11,11 @@ namespace ImageProcessor.Web.Caching /// /// Gets or sets any additional settings required by the cache. /// - Dictionary Settings { get; } + Dictionary Settings { get; set; } string CachedPath { get; } - int MaxAge { get; } + int MaxDays { get; } Task IsNewOrUpdatedAsync(); diff --git a/src/ImageProcessor.Web/Caching/ImageCacheBase.cs b/src/ImageProcessor.Web/Caching/ImageCacheBase.cs index cfb7ea5ce..84517be32 100644 --- a/src/ImageProcessor.Web/Caching/ImageCacheBase.cs +++ b/src/ImageProcessor.Web/Caching/ImageCacheBase.cs @@ -8,6 +8,7 @@ using System.Threading.Tasks; using System.Web; + using ImageProcessor.Web.Configuration; using ImageProcessor.Web.Extensions; using ImageProcessor.Web.Helpers; @@ -28,11 +29,6 @@ /// protected readonly string Querystring; - /// - /// The assembly version. - /// - private static readonly string AssemblyVersion = Assembly.GetExecutingAssembly().GetName().Version.ToString(); - /// /// Initializes a new instance of the class. /// @@ -50,16 +46,17 @@ this.RequestPath = requestPath; this.FullPath = fullPath; this.Querystring = querystring; + this.Settings = ImageProcessorConfiguration.Instance.ImageCacheSettings; } /// - /// Gets any additional settings required by the cache. + /// Gets or sets any additional settings required by the cache. /// public Dictionary Settings { get; set; } public string CachedPath { get; protected set; } - public abstract int MaxAge { get; } + public abstract int MaxDays { get; } public abstract Task IsNewOrUpdatedAsync(); @@ -123,7 +120,7 @@ /// protected virtual bool IsExpired(DateTime creationDate) { - return creationDate.AddDays(this.MaxAge) < DateTime.UtcNow.AddDays(-this.MaxAge); + return creationDate.AddDays(this.MaxDays) < DateTime.UtcNow.AddDays(-this.MaxDays); } } } diff --git a/src/ImageProcessor.Web/Configuration/ImageCacheSection.cs b/src/ImageProcessor.Web/Configuration/ImageCacheSection.cs index 1f868fc04..2aa05cc04 100644 --- a/src/ImageProcessor.Web/Configuration/ImageCacheSection.cs +++ b/src/ImageProcessor.Web/Configuration/ImageCacheSection.cs @@ -14,7 +14,6 @@ namespace ImageProcessor.Web.Configuration using System.IO; using System.Xml; - using ImageProcessor.Web.Extensions; using ImageProcessor.Web.Helpers; /// @@ -23,43 +22,34 @@ namespace ImageProcessor.Web.Configuration public sealed class ImageCacheSection : ConfigurationSection { /// - /// Gets or sets the virtual path of the cache folder. + /// Gets or sets the name of the current cache provider. /// /// The name of the cache folder. - [ConfigurationProperty("virtualPath", DefaultValue = "~/app_data/cache", IsRequired = true)] - [StringValidator(MinLength = 3, MaxLength = 256)] - public string VirtualPath + [ConfigurationProperty("currentCache", DefaultValue = "DiskCache", IsRequired = true)] + public string CurrentCache { get { - string virtualPath = (string)this["virtualPath"]; - - return virtualPath.IsValidVirtualPathName() ? virtualPath : "~/app_data/cache"; + return (string)this["currentCache"]; } set { - this["virtualPath"] = value; + this["currentCache"] = value; } } /// - /// Gets or sets the maximum number of days to store an image in the cache. + /// Gets the /// - /// The maximum number of days to store an image in the cache. - /// Defaults to 28 if not set. - [ConfigurationProperty("maxDays", DefaultValue = "365", IsRequired = false)] - [IntegerValidator(ExcludeRange = false, MinValue = 0)] - public int MaxDays + /// The + [ConfigurationProperty("caches", IsRequired = true)] + public CacheElementCollection ImageCaches { get { - return (int)this["maxDays"]; - } - - set - { - this["maxDays"] = value; + object o = this["caches"]; + return o as CacheElementCollection; } } @@ -83,5 +73,128 @@ namespace ImageProcessor.Web.Configuration return imageCacheSection; } + + /// + /// Represents a CacheElement configuration element within the configuration. + /// + public class CacheElement : ConfigurationElement + { + /// + /// Gets or sets the name of the cache. + /// + /// The name of the service. + [ConfigurationProperty("name", DefaultValue = "", IsRequired = true)] + public string Name + { + get { return (string)this["name"]; } + + set { this["name"] = value; } + } + + /// + /// Gets or sets the type of the cache. + /// + /// The full Type definition of the service + [ConfigurationProperty("type", DefaultValue = "", IsRequired = true)] + public string Type + { + get { return (string)this["type"]; } + + set { this["type"] = value; } + } + + /// + /// Gets the . + /// + /// + /// The . + /// + [ConfigurationProperty("settings", IsRequired = false)] + public SettingElementCollection Settings + { + get + { + return this["settings"] as SettingElementCollection; + } + } + } + + /// + /// Represents a collection of elements within the configuration. + /// + public class CacheElementCollection : ConfigurationElementCollection + { + /// + /// Gets the type of the . + /// + /// + /// The of this collection. + /// + public override ConfigurationElementCollectionType CollectionType + { + get { return ConfigurationElementCollectionType.BasicMap; } + } + + /// + /// Gets the name used to identify this collection of elements in the configuration file when overridden in a derived class. + /// + /// + /// The name of the collection; otherwise, an empty string. The default is an empty string. + /// + protected override string ElementName + { + get { return "cache"; } + } + + /// + /// Gets or sets the + /// at the specified index within the collection. + /// + /// The index at which to get the specified object. + /// + /// The + /// at the specified index within the collection. + /// + public CacheElement this[int index] + { + get + { + return (CacheElement)BaseGet(index); + } + + set + { + if (this.BaseGet(index) != null) + { + this.BaseRemoveAt(index); + } + + this.BaseAdd(index, value); + } + } + + /// + /// When overridden in a derived class, creates a new . + /// + /// + /// A new . + /// + protected override ConfigurationElement CreateNewElement() + { + return new CacheElement(); + } + + /// + /// Gets the element key for a specified configuration element when overridden in a derived class. + /// + /// + /// An that acts as the key for the specified . + /// + /// The to return the key for. + protected override object GetElementKey(ConfigurationElement element) + { + return ((CacheElement)element).Name; + } + } } } diff --git a/src/ImageProcessor.Web/Configuration/ImageProcessingSection.cs b/src/ImageProcessor.Web/Configuration/ImageProcessingSection.cs index cbed1468d..26fb47b8a 100644 --- a/src/ImageProcessor.Web/Configuration/ImageProcessingSection.cs +++ b/src/ImageProcessor.Web/Configuration/ImageProcessingSection.cs @@ -131,10 +131,10 @@ namespace ImageProcessor.Web.Configuration public class PresetElementCollection : ConfigurationElementCollection { /// - /// Gets the type of the . + /// Gets the type of the . /// /// - /// The of this collection. + /// The of this collection. /// public override ConfigurationElementCollectionType CollectionType { @@ -194,7 +194,7 @@ namespace ImageProcessor.Web.Configuration /// Gets the element key for a specified PluginElement configuration element. /// /// - /// The ConfigurationElement + /// The ConfigurationElement /// to return the key for. /// /// The element key for a specified PluginElement configuration element. @@ -255,10 +255,10 @@ namespace ImageProcessor.Web.Configuration public class PluginElementCollection : ConfigurationElementCollection { /// - /// Gets the type of the . + /// Gets the type of the . /// /// - /// The of this collection. + /// The of this collection. /// public override ConfigurationElementCollectionType CollectionType { @@ -318,7 +318,7 @@ namespace ImageProcessor.Web.Configuration /// Gets the element key for a specified PluginElement configuration element. /// /// - /// The ConfigurationElement + /// The ConfigurationElement /// to return the key for. /// /// The element key for a specified PluginElement configuration element. @@ -327,149 +327,5 @@ namespace ImageProcessor.Web.Configuration return ((PluginElement)element).Name; } } - - /// - /// Represents a SettingElement configuration element within the configuration. - /// - public class SettingElement : ConfigurationElement - { - /// - /// Gets or sets the key of the plugin setting. - /// - /// The key of the plugin setting. - [ConfigurationProperty("key", IsRequired = true, IsKey = true)] - public string Key - { - get - { - return this["key"] as string; - } - - set - { - this["key"] = value; - } - } - - /// - /// Gets or sets the value of the plugin setting. - /// - /// The value of the plugin setting. - [ConfigurationProperty("value", IsRequired = true)] - public string Value - { - get - { - return (string)this["value"]; - } - - set - { - this["value"] = value; - } - } - } - - /// - /// Represents a SettingElementCollection collection configuration element within the configuration. - /// - public class SettingElementCollection : ConfigurationElementCollection - { - /// - /// Gets the type of the . - /// - /// - /// The of this collection. - /// - public override ConfigurationElementCollectionType CollectionType - { - get { return ConfigurationElementCollectionType.BasicMap; } - } - - /// - /// Gets the name used to identify this collection of elements in the configuration file when overridden in a derived class. - /// - /// - /// The name of the collection; otherwise, an empty string. The default is an empty string. - /// - protected override string ElementName - { - get { return "setting"; } - } - - /// - /// Gets or sets the - /// at the specified index within the collection. - /// - /// The index at which to get the specified object. - /// - /// The - /// at the specified index within the collection. - /// - public SettingElement this[int index] - { - get - { - return (SettingElement)BaseGet(index); - } - - set - { - if (this.BaseGet(index) != null) - { - this.BaseRemoveAt(index); - } - - this.BaseAdd(index, value); - } - } - - /// - /// Returns the setting element with the specified key. - /// - /// the key representing the element - /// the setting element - public new SettingElement this[string key] - { - get { return (SettingElement)BaseGet(key); } - } - - /// - /// Returns a value indicating whether the settings collection contains the - /// given object. - /// - /// The key to identify the setting. - /// True if the collection contains the key; otherwise false. - public bool ContainsKey(string key) - { - object[] keys = BaseGetAllKeys(); - - return keys.Any(obj => (string)obj == key); - } - - /// - /// Gets the element key for a specified PluginElement configuration element. - /// - /// - /// The ConfigurationElement - /// to return the key for. - /// - /// The element key for a specified PluginElement configuration element. - protected override object GetElementKey(ConfigurationElement element) - { - return ((SettingElement)element).Key; - } - - /// - /// Creates a new SettingElement configuration element. - /// - /// - /// A new SettingElement configuration element. - /// - protected override ConfigurationElement CreateNewElement() - { - return new SettingElement(); - } - } } } diff --git a/src/ImageProcessor.Web/Configuration/ImageProcessorConfiguration.cs b/src/ImageProcessor.Web/Configuration/ImageProcessorConfiguration.cs index 4846c61ea..aaa3a3c4f 100644 --- a/src/ImageProcessor.Web/Configuration/ImageProcessorConfiguration.cs +++ b/src/ImageProcessor.Web/Configuration/ImageProcessorConfiguration.cs @@ -19,6 +19,7 @@ namespace ImageProcessor.Web.Configuration using ImageProcessor.Common.Extensions; using ImageProcessor.Processors; + using ImageProcessor.Web.Caching; using ImageProcessor.Web.Processors; using ImageProcessor.Web.Services; @@ -67,6 +68,7 @@ namespace ImageProcessor.Web.Configuration { this.LoadGraphicsProcessors(); this.LoadImageServices(); + this.LoadImageCache(); } #endregion @@ -93,41 +95,26 @@ namespace ImageProcessor.Web.Configuration public IList ImageServices { get; private set; } /// - /// Gets a value indicating whether to preserve exif meta data. + /// Gets the current image cache. /// - public bool PreserveExifMetaData - { - get - { - return GetImageProcessingSection().PreserveExifMetaData; - } - } + public Type ImageCache { get; private set; } - #region Caching /// - /// Gets the maximum number of days to store images in the cache. + /// Gets the image cache settings. /// - public int MaxCacheDays - { - get - { - return GetImageCacheSection().MaxDays; - } - } + public Dictionary ImageCacheSettings { get; private set; } /// - /// Gets or the virtual path of the cache folder. + /// Gets a value indicating whether to preserve exif meta data. /// - /// The virtual path of the cache folder. - public string VirtualCachePath + public bool PreserveExifMetaData { get { - return GetImageCacheSection().VirtualPath; + return GetImageProcessingSection().PreserveExifMetaData; } } #endregion - #endregion #region Methods /// @@ -271,7 +258,7 @@ namespace ImageProcessor.Web.Configuration if (pluginElement != null) { settings = pluginElement.Settings - .Cast() + .Cast() .ToDictionary(setting => setting.Key, setting => setting.Value); } else @@ -367,13 +354,13 @@ namespace ImageProcessor.Web.Configuration } /// - /// Returns the for the given plugin. + /// Returns the for the given plugin. /// /// /// The name of the plugin to get the settings for. /// /// - /// The for the given plugin. + /// The for the given plugin. /// private Dictionary GetServiceSettings(string name) { @@ -387,7 +374,7 @@ namespace ImageProcessor.Web.Configuration if (serviceElement != null) { settings = serviceElement.Settings - .Cast() + .Cast() .ToDictionary(setting => setting.Key, setting => setting.Value); } else @@ -424,6 +411,39 @@ namespace ImageProcessor.Web.Configuration return whitelist; } #endregion + + #region ImageCaches + /// + /// Gets the currently assigned . + /// + private void LoadImageCache() + { + if (this.ImageCache == null) + { + string curentCache = GetImageCacheSection().CurrentCache; + ImageCacheSection.CacheElementCollection caches = imageCacheSection.ImageCaches; + + foreach (ImageCacheSection.CacheElement cache in caches) + { + if (cache.Name == curentCache) + { + Type type = Type.GetType(cache.Type); + + if (type == null) + { + throw new TypeLoadException("Couldn't load IImageCache: " + cache.Type); + } + + this.ImageCache = type; + this.ImageCacheSettings = cache.Settings + .Cast() + .ToDictionary(setting => setting.Key, setting => setting.Value); + break; + } + } + } + } + #endregion #endregion } } \ No newline at end of file diff --git a/src/ImageProcessor.Web/Configuration/ImageSecuritySection.cs b/src/ImageProcessor.Web/Configuration/ImageSecuritySection.cs index c99fcb3ae..2795cf710 100644 --- a/src/ImageProcessor.Web/Configuration/ImageSecuritySection.cs +++ b/src/ImageProcessor.Web/Configuration/ImageSecuritySection.cs @@ -13,7 +13,6 @@ namespace ImageProcessor.Web.Configuration using System; using System.Configuration; using System.IO; - using System.Linq; using System.Xml; using ImageProcessor.Web.Helpers; @@ -23,11 +22,10 @@ namespace ImageProcessor.Web.Configuration /// public sealed class ImageSecuritySection : ConfigurationSection { - #region Properties /// - /// Gets the + /// Gets the /// - /// The + /// The [ConfigurationProperty("services", IsRequired = true)] public ServiceElementCollection ImageServices { @@ -42,9 +40,7 @@ namespace ImageProcessor.Web.Configuration /// Gets or sets a value indicating whether to auto load services. /// public bool AutoLoadServices { get; set; } - #endregion - #region Methods /// /// Retrieves the security configuration section from the current application configuration. /// @@ -66,7 +62,6 @@ namespace ImageProcessor.Web.Configuration imageSecuritySection.AutoLoadServices = true; return imageSecuritySection; } - #endregion /// /// Represents a ServiceElement configuration element within the configuration. @@ -110,10 +105,10 @@ namespace ImageProcessor.Web.Configuration } /// - /// Gets the . + /// Gets the . /// /// - /// The . + /// The . /// [ConfigurationProperty("settings", IsRequired = false)] public SettingElementCollection Settings @@ -146,10 +141,10 @@ namespace ImageProcessor.Web.Configuration public class ServiceElementCollection : ConfigurationElementCollection { /// - /// Gets the type of the . + /// Gets the type of the . /// /// - /// The of this collection. + /// The of this collection. /// public override ConfigurationElementCollectionType CollectionType { @@ -195,10 +190,10 @@ namespace ImageProcessor.Web.Configuration } /// - /// When overridden in a derived class, creates a new . + /// When overridden in a derived class, creates a new . /// /// - /// A new . + /// A new . /// protected override ConfigurationElement CreateNewElement() { @@ -209,159 +204,15 @@ namespace ImageProcessor.Web.Configuration /// Gets the element key for a specified configuration element when overridden in a derived class. /// /// - /// An that acts as the key for the specified . + /// An that acts as the key for the specified . /// - /// The to return the key for. + /// The to return the key for. protected override object GetElementKey(ConfigurationElement element) { return ((ServiceElement)element).Name; } } - /// - /// Represents a SettingElement configuration element within the configuration. - /// - public class SettingElement : ConfigurationElement - { - /// - /// Gets or sets the key of the plugin setting. - /// - /// The key of the plugin setting. - [ConfigurationProperty("key", IsRequired = true, IsKey = true)] - public string Key - { - get - { - return this["key"] as string; - } - - set - { - this["key"] = value; - } - } - - /// - /// Gets or sets the value of the plugin setting. - /// - /// The value of the plugin setting. - [ConfigurationProperty("value", IsRequired = true)] - public string Value - { - get - { - return (string)this["value"]; - } - - set - { - this["value"] = value; - } - } - } - - /// - /// Represents a SettingElementCollection collection configuration element within the configuration. - /// - public class SettingElementCollection : ConfigurationElementCollection - { - /// - /// Gets the type of the . - /// - /// - /// The of this collection. - /// - public override ConfigurationElementCollectionType CollectionType - { - get { return ConfigurationElementCollectionType.BasicMap; } - } - - /// - /// Gets the name used to identify this collection of elements in the configuration file when overridden in a derived class. - /// - /// - /// The name of the collection; otherwise, an empty string. The default is an empty string. - /// - protected override string ElementName - { - get { return "setting"; } - } - - /// - /// Gets or sets the - /// at the specified index within the collection. - /// - /// The index at which to get the specified object. - /// - /// The - /// at the specified index within the collection. - /// - public SettingElement this[int index] - { - get - { - return (SettingElement)BaseGet(index); - } - - set - { - if (this.BaseGet(index) != null) - { - this.BaseRemoveAt(index); - } - - this.BaseAdd(index, value); - } - } - - /// - /// Returns the setting element with the specified key. - /// - /// the key representing the element - /// the setting element - public new SettingElement this[string key] - { - get { return (SettingElement)BaseGet(key); } - } - - /// - /// Returns a value indicating whether the settings collection contains the - /// given object. - /// - /// The key to identify the setting. - /// True if the collection contains the key; otherwise false. - public bool ContainsKey(string key) - { - object[] keys = BaseGetAllKeys(); - - return keys.Any(obj => (string)obj == key); - } - - /// - /// Gets the element key for a specified PluginElement configuration element. - /// - /// - /// The ConfigurationElement - /// to return the key for. - /// - /// The element key for a specified PluginElement configuration element. - protected override object GetElementKey(ConfigurationElement element) - { - return ((SettingElement)element).Key; - } - - /// - /// Creates a new SettingElement configuration element. - /// - /// - /// A new SettingElement configuration element. - /// - protected override ConfigurationElement CreateNewElement() - { - return new SettingElement(); - } - } - /// /// Represents a whitelist collection configuration element within the configuration. /// @@ -404,7 +255,7 @@ namespace ImageProcessor.Web.Configuration /// /// Gets the element key for a specified whitelist configuration element. /// - /// The ConfigurationElement to return the key for. + /// The ConfigurationElement to return the key for. /// The element key for a specified whitelist configuration element. protected override object GetElementKey(ConfigurationElement element) { diff --git a/src/ImageProcessor.Web/Configuration/Resources/cache.config b/src/ImageProcessor.Web/Configuration/Resources/cache.config index c9b64a68a..f54e36d7a 100644 --- a/src/ImageProcessor.Web/Configuration/Resources/cache.config +++ b/src/ImageProcessor.Web/Configuration/Resources/cache.config @@ -1 +1,10 @@ - + + + + + + + + + + diff --git a/src/ImageProcessor.Web/Configuration/Shared/SettingElement.cs b/src/ImageProcessor.Web/Configuration/Shared/SettingElement.cs new file mode 100644 index 000000000..804d77ec0 --- /dev/null +++ b/src/ImageProcessor.Web/Configuration/Shared/SettingElement.cs @@ -0,0 +1,56 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) James South. +// Licensed under the Apache License, Version 2.0. +// +// +// Represents a SettingElement configuration element within the configuration. +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace ImageProcessor.Web.Configuration +{ + using System.Configuration; + + /// + /// Represents a SettingElement configuration element within the configuration. + /// + public class SettingElement : ConfigurationElement + { + /// + /// Gets or sets the key of the plugin setting. + /// + /// The key of the plugin setting. + [ConfigurationProperty("key", IsRequired = true, IsKey = true)] + public string Key + { + get + { + return this["key"] as string; + } + + set + { + this["key"] = value; + } + } + + /// + /// Gets or sets the value of the plugin setting. + /// + /// The value of the plugin setting. + [ConfigurationProperty("value", IsRequired = true)] + public string Value + { + get + { + return (string)this["value"]; + } + + set + { + this["value"] = value; + } + } + } +} diff --git a/src/ImageProcessor.Web/Configuration/Shared/SettingElementCollection.cs b/src/ImageProcessor.Web/Configuration/Shared/SettingElementCollection.cs new file mode 100644 index 000000000..d060a200e --- /dev/null +++ b/src/ImageProcessor.Web/Configuration/Shared/SettingElementCollection.cs @@ -0,0 +1,117 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) James South. +// Licensed under the Apache License, Version 2.0. +// +// +// Represents a SettingElementCollection collection configuration element within the configuration. +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace ImageProcessor.Web.Configuration +{ + using System.Configuration; + using System.Linq; + + /// + /// Represents a SettingElementCollection collection configuration element within the configuration. + /// + public class SettingElementCollection : ConfigurationElementCollection + { + /// + /// Gets the type of the . + /// + /// + /// The of this collection. + /// + public override ConfigurationElementCollectionType CollectionType + { + get { return ConfigurationElementCollectionType.BasicMap; } + } + + /// + /// Gets the name used to identify this collection of elements in the configuration file when overridden in a derived class. + /// + /// + /// The name of the collection; otherwise, an empty string. The default is an empty string. + /// + protected override string ElementName + { + get { return "setting"; } + } + + /// + /// Gets or sets the + /// at the specified index within the collection. + /// + /// The index at which to get the specified object. + /// + /// The + /// at the specified index within the collection. + /// + public SettingElement this[int index] + { + get + { + return (SettingElement)BaseGet(index); + } + + set + { + if (this.BaseGet(index) != null) + { + this.BaseRemoveAt(index); + } + + this.BaseAdd(index, value); + } + } + + /// + /// Returns the setting element with the specified key. + /// + /// the key representing the element + /// the setting element + public new SettingElement this[string key] + { + get { return (SettingElement)BaseGet(key); } + } + + /// + /// Returns a value indicating whether the settings collection contains the + /// given object. + /// + /// The key to identify the setting. + /// True if the collection contains the key; otherwise false. + public bool ContainsKey(string key) + { + object[] keys = BaseGetAllKeys(); + + return keys.Any(obj => (string)obj == key); + } + + /// + /// Gets the element key for a specified PluginElement configuration element. + /// + /// + /// The ConfigurationElement + /// to return the key for. + /// + /// The element key for a specified PluginElement configuration element. + protected override object GetElementKey(ConfigurationElement element) + { + return ((SettingElement)element).Key; + } + + /// + /// Creates a new SettingElement configuration element. + /// + /// + /// A new SettingElement configuration element. + /// + protected override ConfigurationElement CreateNewElement() + { + return new SettingElement(); + } + } +} diff --git a/src/ImageProcessor.Web/Extensions/TypeInitializationExtensions.cs b/src/ImageProcessor.Web/Extensions/TypeInitializationExtensions.cs new file mode 100644 index 000000000..bd59d1b3b --- /dev/null +++ b/src/ImageProcessor.Web/Extensions/TypeInitializationExtensions.cs @@ -0,0 +1,180 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) James South. +// Licensed under the Apache License, Version 2.0. +// +// +// Extensions methods for for creating instances of types faster than +// using reflection. Modified from the original class at. +// +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace ImageProcessor.Web.Extensions +{ + using System; + using System.Collections.Concurrent; + using System.Linq; + using System.Linq.Expressions; + using System.Reflection; + + /// + /// Extensions methods for for creating instances of types faster than + /// using reflection. Modified from the original class at. + /// + /// + internal static class TypeInitializationExtensions + { + /// + /// Returns an instance of the on which the method is invoked. + /// + /// The type on which the method was invoked. + /// An instance of the . + public static object GetInstance(this Type type) + { + // This is about as quick as it gets. + return Activator.CreateInstance(type); + } + + /// + /// Returns an instance of the on which the method is invoked. + /// + /// The type of the argument to pass to the constructor. + /// The type on which the method was invoked. + /// The argument to pass to the constructor. + /// An instance of the given . + public static object GetInstance(this Type type, TArg argument) + { + return GetInstance(type, argument, null); + } + + /// + /// Returns an instance of the on which the method is invoked. + /// + /// The type of the first argument to pass to the constructor. + /// The type of the second argument to pass to the constructor. + /// The type on which the method was invoked. + /// The first argument to pass to the constructor. + /// The second argument to pass to the constructor. + /// An instance of the given . + public static object GetInstance(this Type type, TArg1 argument1, TArg2 argument2) + { + return GetInstance(type, argument1, argument2, null); + } + + /// + /// Returns an instance of the on which the method is invoked. + /// + /// The type of the first argument to pass to the constructor. + /// The type of the second argument to pass to the constructor. + /// The type of the third argument to pass to the constructor. + /// The type on which the method was invoked. + /// The first argument to pass to the constructor. + /// The second argument to pass to the constructor. + /// The third argument to pass to the constructor. + /// An instance of the given . + public static object GetInstance( + this Type type, + TArg1 argument1, + TArg2 argument2, + TArg3 argument3) + { + return InstanceCreationFactory + .CreateInstanceOf(type, argument1, argument2, argument3); + } + + /// + /// The instance creation factory for creating instances. + /// + /// The type of the first argument to pass to the constructor. + /// The type of the second argument to pass to the constructor. + /// The type of the third argument to pass to the constructor. + private static class InstanceCreationFactory + { + /// + /// This dictionary will hold a cache of object-creation functions, keyed by the Type to create: + /// + private static readonly ConcurrentDictionary> InstanceCreationMethods = new ConcurrentDictionary>(); + + /// + /// The create instance of. + /// + /// + /// The type. + /// + /// The first argument to pass to the constructor. + /// The second argument to pass to the constructor. + /// The third argument to pass to the constructor. + /// + /// The . + /// + public static object CreateInstanceOf(Type type, TArg1 arg1, TArg2 arg2, TArg3 arg3) + { + CacheInstanceCreationMethodIfRequired(type); + + return InstanceCreationMethods[type].Invoke(arg1, arg2, arg3); + } + + /// + /// Caches the instance creation method. + /// + /// + /// The who's constructor to cache. + /// + private static void CacheInstanceCreationMethodIfRequired(Type type) + { + // Bail out if we've already cached the instance creation method: + Func cached; + if (InstanceCreationMethods.TryGetValue(type, out cached)) + { + return; + } + + Type[] argumentTypes = { typeof(TArg1), typeof(TArg2), typeof(TArg3) }; + + // Get a collection of the constructor argument Types we've been given; ignore any + // arguments which are of the 'ignore this' Type: + Type[] constructorArgumentTypes = argumentTypes.Where(t => t != typeof(TypeToIgnore)).ToArray(); + + // Get the Constructor which matches the given argument Types: + ConstructorInfo constructor = type.GetConstructor( + BindingFlags.Instance | BindingFlags.Public, + null, + CallingConventions.HasThis, + constructorArgumentTypes, + new ParameterModifier[0]); + + // Get a set of Expressions representing the parameters which will be passed to the Func: + ParameterExpression[] lamdaParameterExpressions = + { + Expression.Parameter(typeof(TArg1), "param1"), + Expression.Parameter(typeof(TArg2), "param2"), + Expression.Parameter(typeof(TArg3), "param3") + }; + + // Get a set of Expressions representing the parameters which will be passed to the constructor: + ParameterExpression[] constructorParameterExpressions = + lamdaParameterExpressions.Take(constructorArgumentTypes.Length).ToArray(); + + // Get an Expression representing the constructor call, passing in the constructor parameters: + NewExpression constructorCallExpression = Expression.New(constructor, constructorParameterExpressions.Cast()); + + // Compile the Expression into a Func which takes three arguments and returns the constructed object: + Func constructorCallingLambda = + Expression.Lambda>( + constructorCallExpression, + lamdaParameterExpressions).Compile(); + + InstanceCreationMethods.TryAdd(type, constructorCallingLambda); + } + } + + /// + /// To allow for overloads with differing numbers of arguments, we flag arguments which should be + /// ignored by using this Type: + /// + private class TypeToIgnore + { + } + } +} diff --git a/src/ImageProcessor.Web/Helpers/TypePropertyHelpers.cs b/src/ImageProcessor.Web/Helpers/TypePropertyHelpers.cs new file mode 100644 index 000000000..0b3d850cb --- /dev/null +++ b/src/ImageProcessor.Web/Helpers/TypePropertyHelpers.cs @@ -0,0 +1,19 @@ +namespace ImageProcessor.Web.Extensions +{ + using System; + using System.Linq.Expressions; + + internal static class TypePropertyHelpers + { + public static string GetPropertyName(Expression> expression) + { + MemberExpression member = expression.Body as MemberExpression; + if (member != null) + { + return member.Member.Name; + } + + throw new ArgumentException("expression"); + } + } +} diff --git a/src/ImageProcessor.Web/HttpModules/ImageProcessingModule.cs b/src/ImageProcessor.Web/HttpModules/ImageProcessingModule.cs index 5015c2ac4..caf79a5b6 100644 --- a/src/ImageProcessor.Web/HttpModules/ImageProcessingModule.cs +++ b/src/ImageProcessor.Web/HttpModules/ImageProcessingModule.cs @@ -3,34 +3,24 @@ // Copyright (c) James South. // Licensed under the Apache License, Version 2.0. // -// -// Processes any image requests within the web application. -// // -------------------------------------------------------------------------------------------------------------------- - namespace ImageProcessor.Web.HttpModules { - #region Using using System; using System.Collections.Generic; using System.IO; using System.Linq; - using System.Net; using System.Reflection; - using System.Security; - using System.Security.Permissions; - using System.Security.Principal; using System.Text.RegularExpressions; using System.Threading.Tasks; using System.Web; using System.Web.Hosting; - using System.Web.Security; using ImageProcessor.Web.Caching; using ImageProcessor.Web.Configuration; + using ImageProcessor.Web.Extensions; using ImageProcessor.Web.Helpers; using ImageProcessor.Web.Services; - #endregion /// /// Processes any image requests within the web application. @@ -38,6 +28,7 @@ namespace ImageProcessor.Web.HttpModules public sealed class ImageProcessingModule : IHttpModule { #region Fields + /// /// The key for storing the response type of the current image. /// @@ -86,10 +77,14 @@ namespace ImageProcessor.Web.HttpModules /// private bool isDisposed; + /// + /// The image cache. + /// private IImageCache imageCache; #endregion #region Destructors + /// /// Finalizes an instance of the class. /// @@ -107,6 +102,7 @@ namespace ImageProcessor.Web.HttpModules // readability and maintainability. this.Dispose(false); } + #endregion /// @@ -132,6 +128,7 @@ namespace ImageProcessor.Web.HttpModules public static event ProcessQuerystringEventHandler OnProcessQuerystring; #region IHttpModule Members + /// /// Initializes a module and prepares it to handle requests. /// @@ -174,7 +171,9 @@ namespace ImageProcessor.Web.HttpModules /// /// Disposes the object and frees resources for the Garbage Collector. /// - /// If true, the object gets disposed. + /// + /// If true, the object gets disposed. + /// private void Dispose(bool disposing) { if (this.isDisposed) @@ -192,6 +191,7 @@ namespace ImageProcessor.Web.HttpModules // Note disposing is done. this.isDisposed = true; } + #endregion /// @@ -249,8 +249,12 @@ namespace ImageProcessor.Web.HttpModules /// /// Occurs just before ASP.NET send HttpHeaders to the client. /// - /// The source of the event. - /// An EventArgs that contains the event data. + /// + /// The source of the event. + /// + /// + /// An EventArgs that contains the event data. + /// private void ContextPreSendRequestHeaders(object sender, EventArgs e) { HttpContext context = ((HttpApplication)sender).Context; @@ -373,7 +377,8 @@ namespace ImageProcessor.Web.HttpModules } // Create a new cache to help process and cache the request. - this.imageCache = new DiskCache2(requestPath, fullPath, queryString); + this.imageCache = (IImageCache)ImageProcessorConfiguration.Instance + .ImageCache.GetInstance(requestPath, fullPath, queryString); // Is the file new or updated? bool isNewOrUpdated = await this.imageCache.IsNewOrUpdatedAsync(); @@ -493,7 +498,7 @@ namespace ImageProcessor.Web.HttpModules cache.SetLastModifiedFromFileDependencies(); } - int maxDays = this.imageCache.MaxAge; + int maxDays = this.imageCache.MaxDays; cache.SetExpires(DateTime.Now.ToUniversalTime().AddDays(maxDays)); cache.SetMaxAge(new TimeSpan(maxDays, 0, 0, 0)); @@ -587,6 +592,7 @@ namespace ImageProcessor.Web.HttpModules // Return the file based service return services.FirstOrDefault(s => string.IsNullOrWhiteSpace(s.Prefix) && s.IsValidRequest(path)); } + #endregion } } \ No newline at end of file diff --git a/src/ImageProcessor.Web/ImageProcessor.Web.csproj b/src/ImageProcessor.Web/ImageProcessor.Web.csproj index 508bc4d43..642a6d634 100644 --- a/src/ImageProcessor.Web/ImageProcessor.Web.csproj +++ b/src/ImageProcessor.Web/ImageProcessor.Web.csproj @@ -46,9 +46,13 @@ - + + + + + diff --git a/src/ImageProcessor.Web/ImageProcessor.Web.csproj.DotSettings b/src/ImageProcessor.Web/ImageProcessor.Web.csproj.DotSettings new file mode 100644 index 000000000..ce2072422 --- /dev/null +++ b/src/ImageProcessor.Web/ImageProcessor.Web.csproj.DotSettings @@ -0,0 +1,2 @@ + + True \ No newline at end of file diff --git a/src/TestWebsites/MVC/Test_Website_MVC.csproj b/src/TestWebsites/MVC/Test_Website_MVC.csproj index 6c6b551c4..3351fa9bf 100644 --- a/src/TestWebsites/MVC/Test_Website_MVC.csproj +++ b/src/TestWebsites/MVC/Test_Website_MVC.csproj @@ -152,6 +152,10 @@ + + {3c805e4c-d679-43f8-8c43-8909cdb4d4d7} + ImageProcessor.Web.AzureBlobCache + {55d08737-7d7e-4995-8892-bd9f944329e6} ImageProcessor.Web.PostProcessor diff --git a/src/TestWebsites/MVC/config/imageprocessor/cache.config b/src/TestWebsites/MVC/config/imageprocessor/cache.config index e4a9c5e9a..38bce1f29 100644 --- a/src/TestWebsites/MVC/config/imageprocessor/cache.config +++ b/src/TestWebsites/MVC/config/imageprocessor/cache.config @@ -1,3 +1,11 @@ - - + + + + + + + + + +