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 @@
-
-
+
+
+
+
+
+
+
+
+
+