Browse Source

Tie up cache to configuration.

Former-commit-id: 1ba1d6f6fdba545f9659d634cc9bb1e6e0833758
Former-commit-id: 192eccd79658cb84e31c22e23c0f4c9690dbe314
af/merge-core
James South 11 years ago
parent
commit
f6a61e2c39
  1. 66
      src/ImageProcessor.Web.AzureBlobCache/AzureBlobCache.cs
  2. 30
      src/ImageProcessor.Web/Caching/CacheIndexer.cs
  3. 2
      src/ImageProcessor.Web/Caching/CachedImage.cs
  4. 297
      src/ImageProcessor.Web/Caching/DiskCache.cs
  5. 168
      src/ImageProcessor.Web/Caching/DiskCache2.cs
  6. 4
      src/ImageProcessor.Web/Caching/IImageCache.cs
  7. 13
      src/ImageProcessor.Web/Caching/ImageCacheBase.cs
  8. 155
      src/ImageProcessor.Web/Configuration/ImageCacheSection.cs
  9. 156
      src/ImageProcessor.Web/Configuration/ImageProcessingSection.cs
  10. 72
      src/ImageProcessor.Web/Configuration/ImageProcessorConfiguration.cs
  11. 171
      src/ImageProcessor.Web/Configuration/ImageSecuritySection.cs
  12. 11
      src/ImageProcessor.Web/Configuration/Resources/cache.config
  13. 56
      src/ImageProcessor.Web/Configuration/Shared/SettingElement.cs
  14. 117
      src/ImageProcessor.Web/Configuration/Shared/SettingElementCollection.cs
  15. 180
      src/ImageProcessor.Web/Extensions/TypeInitializationExtensions.cs
  16. 19
      src/ImageProcessor.Web/Helpers/TypePropertyHelpers.cs
  17. 38
      src/ImageProcessor.Web/HttpModules/ImageProcessingModule.cs
  18. 6
      src/ImageProcessor.Web/ImageProcessor.Web.csproj
  19. 2
      src/ImageProcessor.Web/ImageProcessor.Web.csproj.DotSettings
  20. 4
      src/TestWebsites/MVC/Test_Website_MVC.csproj
  21. 12
      src/TestWebsites/MVC/config/imageprocessor/cache.config

66
src/ImageProcessor.Web.AzureBlobCache/AzureBlobCache.cs

@ -22,7 +22,7 @@
/// <summary>
/// The max age.
/// </summary>
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<string, string>();
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<CloudBlockBlob>()
.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();
}
}

30
src/ImageProcessor.Web/Caching/CacheIndexer.cs

@ -15,9 +15,9 @@ namespace ImageProcessor.Web.Caching
using System.Runtime.Caching;
/// <summary>
/// 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.
/// </summary>
internal static class CacheIndexer
public static class CacheIndexer
{
#region Public
/// <summary>
@ -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<string> { cachedImage.Path }));
MemCache.AddItem(cachedImage.Key, cachedImage, policy);
MemCache.AddItem(Path.GetFileNameWithoutExtension(cachedImage.Key), cachedImage, policy);
return cachedImage;
}
#endregion

2
src/ImageProcessor.Web/Caching/CachedImage.cs

@ -15,7 +15,7 @@ namespace ImageProcessor.Web.Caching
/// <summary>
/// Describes a cached image
/// </summary>
internal sealed class CachedImage
public sealed class CachedImage
{
/// <summary>
/// Gets or sets the key identifying the cached image.

297
src/ImageProcessor.Web/Caching/DiskCache.cs

@ -1,39 +1,19 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="DiskCache.cs" company="James South">
// Copyright (c) James South.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// <summary>
// The disk cache.
// </summary>
// --------------------------------------------------------------------------------------------------------------------
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
/// <summary>
/// The disk cache.
/// </summary>
internal sealed class DiskCache
public class DiskCache : ImageCacheBase
{
#region Fields
/// <summary>
/// The maximum number of days to cache files on the system for.
/// </summary>
internal static readonly int MaxFileCachedDuration = ImageProcessorConfiguration.Instance.MaxCacheDays;
/// <summary>
/// The maximum number of files allowed in the directory.
/// </summary>
@ -47,42 +27,25 @@ namespace ImageProcessor.Web.Caching
private const int MaxFilesCount = 100;
/// <summary>
/// The virtual cache path.
/// </summary>
private static readonly string VirtualCachePath = ImageProcessorConfiguration.Instance.VirtualCachePath;
/// <summary>
/// The absolute path to virtual cache path on the server.
/// </summary>
private static readonly string AbsoluteCachePath = HostingEnvironment.MapPath(ImageProcessorConfiguration.Instance.VirtualCachePath);
/// <summary>
/// The request path for the image.
/// </summary>
private readonly string requestPath;
/// <summary>
/// The full path for the image.
/// The max age.
/// </summary>
private readonly string fullPath;
private readonly int maxDays;
/// <summary>
/// The querystring containing processing instructions.
/// The virtual cache path.
/// </summary>
private readonly string querystring;
private readonly string virtualCachePath;
/// <summary>
/// The physical cached path.
/// The absolute path to virtual cache path on the server.
/// </summary>
private string physicalCachedPath;
private readonly string absoluteCachePath;
/// <summary>
/// The virtual cached path.
/// The virtual cached path to the cached file.
/// </summary>
private string virtualCachedPath;
#endregion
private string virtualCachedFilePath;
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="DiskCache"/> class.
/// </summary>
@ -96,49 +59,103 @@ namespace ImageProcessor.Web.Caching
/// The querystring containing instructions.
/// </param>
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
/// <summary>
/// Gets the cached path.
/// The maximum number of days to cache files on the system for.
/// TODO: Shift the getter source to proper config.
/// </summary>
public string CachedPath
public override int MaxDays
{
get
{
return this.physicalCachedPath;
return this.maxDays;
}
}
/// <summary>
/// Gets the cached path.
/// </summary>
public string VirtualCachedPath
public override async Task<bool> 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
/// <summary>
/// Trims a cached folder ensuring that it does not exceed the maximum file count.
/// </summary>
/// <param name="path">
/// The path to the folder.
/// </param>
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<FileInfo> 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
}
}
/// <summary>
/// Adds an image to the cache.
/// </summary>
/// <param name="cachedPath">
/// The path to the cached image.
/// </param>
public void AddImageToCache(string cachedPath)
{
string key = Path.GetFileNameWithoutExtension(cachedPath);
CachedImage cachedImage = new CachedImage
{
Key = key,
Path = cachedPath,
CreationTimeUtc = DateTime.UtcNow
};
CacheIndexer.Add(cachedImage);
}
/// <summary>
/// Returns a value indicating whether the original file is new or has been updated.
/// </summary>
/// <param name="cachedPath">
/// The path to the cached image.
/// </param>
/// <returns>
/// True if The original file is new or has been updated; otherwise, false.
/// </returns>
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
/// <summary>
/// Gets a value indicating whether the given images creation date is out with
/// the prescribed limit.
/// </summary>
/// <param name="creationDate">
/// The creation date.
/// </param>
/// <returns>
/// The true if the date is out with the limit, otherwise; false.
/// </returns>
private static bool IsExpired(DateTime creationDate)
{
return creationDate.AddDays(MaxFileCachedDuration) < DateTime.UtcNow.AddDays(-MaxFileCachedDuration);
}
/// <summary>
/// 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.
/// </summary>
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
}
}

168
src/ImageProcessor.Web/Caching/DiskCache2.cs

@ -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
{
/// <summary>
/// The maximum number of files allowed in the directory.
/// </summary>
/// <remarks>
/// 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.
/// <see href="http://stackoverflow.com/questions/197162/ntfs-performance-and-large-volumes-of-files-and-directories"/>
/// <see href="http://stackoverflow.com/questions/115882/how-do-you-deal-with-lots-of-small-files"/>
/// <see href="http://stackoverflow.com/questions/1638219/millions-of-small-graphics-files-and-how-to-overcome-slow-file-system-access-on"/>
/// </remarks>
private const int MaxFilesCount = 100;
/// <summary>
/// The max age.
/// </summary>
private readonly int maxAge;
/// <summary>
/// The virtual cache path.
/// </summary>
private readonly string virtualCachePath;
/// <summary>
/// The absolute path to virtual cache path on the server.
/// </summary>
private readonly string absoluteCachePath;
/// <summary>
/// The virtual cached path to the cached file.
/// </summary>
private string virtualCachedFilePath;
public DiskCache2(string requestPath, string fullPath, string querystring)
: base(requestPath, fullPath, querystring)
{
// TODO: Get from configuration.
this.Settings = new Dictionary<string, string>();
this.maxAge = Convert.ToInt32(this.Settings["MaxAge"]);
this.virtualCachePath = this.Settings["VirtualCachePath"];
this.absoluteCachePath = HostingEnvironment.MapPath(this.virtualCachePath);
}
/// <summary>
/// The maximum number of days to cache files on the system for.
/// TODO: Shift the getter source to proper config.
/// </summary>
public override int MaxAge
{
get
{
return this.maxAge;
}
}
public override async Task<bool> 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<FileInfo> 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);
}
}
}

4
src/ImageProcessor.Web/Caching/IImageCache.cs

@ -11,11 +11,11 @@ namespace ImageProcessor.Web.Caching
/// <summary>
/// Gets or sets any additional settings required by the cache.
/// </summary>
Dictionary<string, string> Settings { get; }
Dictionary<string, string> Settings { get; set; }
string CachedPath { get; }
int MaxAge { get; }
int MaxDays { get; }
Task<bool> IsNewOrUpdatedAsync();

13
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 @@
/// </summary>
protected readonly string Querystring;
/// <summary>
/// The assembly version.
/// </summary>
private static readonly string AssemblyVersion = Assembly.GetExecutingAssembly().GetName().Version.ToString();
/// <summary>
/// Initializes a new instance of the <see cref="ImageCacheBase"/> class.
/// </summary>
@ -50,16 +46,17 @@
this.RequestPath = requestPath;
this.FullPath = fullPath;
this.Querystring = querystring;
this.Settings = ImageProcessorConfiguration.Instance.ImageCacheSettings;
}
/// <summary>
/// Gets any additional settings required by the cache.
/// Gets or sets any additional settings required by the cache.
/// </summary>
public Dictionary<string, string> Settings { get; set; }
public string CachedPath { get; protected set; }
public abstract int MaxAge { get; }
public abstract int MaxDays { get; }
public abstract Task<bool> IsNewOrUpdatedAsync();
@ -123,7 +120,7 @@
/// </returns>
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);
}
}
}

155
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;
/// <summary>
@ -23,43 +22,34 @@ namespace ImageProcessor.Web.Configuration
public sealed class ImageCacheSection : ConfigurationSection
{
/// <summary>
/// Gets or sets the virtual path of the cache folder.
/// Gets or sets the name of the current cache provider.
/// </summary>
/// <value>The name of the cache folder.</value>
[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;
}
}
/// <summary>
/// Gets or sets the maximum number of days to store an image in the cache.
/// Gets the <see cref="CacheElementCollection"/>
/// </summary>
/// <value>The maximum number of days to store an image in the cache.</value>
/// <remarks>Defaults to 28 if not set.</remarks>
[ConfigurationProperty("maxDays", DefaultValue = "365", IsRequired = false)]
[IntegerValidator(ExcludeRange = false, MinValue = 0)]
public int MaxDays
/// <value>The <see cref="CacheElementCollection"/></value>
[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;
}
/// <summary>
/// Represents a CacheElement configuration element within the configuration.
/// </summary>
public class CacheElement : ConfigurationElement
{
/// <summary>
/// Gets or sets the name of the cache.
/// </summary>
/// <value>The name of the service.</value>
[ConfigurationProperty("name", DefaultValue = "", IsRequired = true)]
public string Name
{
get { return (string)this["name"]; }
set { this["name"] = value; }
}
/// <summary>
/// Gets or sets the type of the cache.
/// </summary>
/// <value>The full Type definition of the service</value>
[ConfigurationProperty("type", DefaultValue = "", IsRequired = true)]
public string Type
{
get { return (string)this["type"]; }
set { this["type"] = value; }
}
/// <summary>
/// Gets the <see cref="SettingElementCollection"/>.
/// </summary>
/// <value>
/// The <see cref="SettingElementCollection"/>.
/// </value>
[ConfigurationProperty("settings", IsRequired = false)]
public SettingElementCollection Settings
{
get
{
return this["settings"] as SettingElementCollection;
}
}
}
/// <summary>
/// Represents a collection of <see cref="CacheElement"/> elements within the configuration.
/// </summary>
public class CacheElementCollection : ConfigurationElementCollection
{
/// <summary>
/// Gets the type of the <see cref="ConfigurationElementCollection"/>.
/// </summary>
/// <value>
/// The <see cref="ConfigurationElementCollectionType"/> of this collection.
/// </value>
public override ConfigurationElementCollectionType CollectionType
{
get { return ConfigurationElementCollectionType.BasicMap; }
}
/// <summary>
/// Gets the name used to identify this collection of elements in the configuration file when overridden in a derived class.
/// </summary>
/// <value>
/// The name of the collection; otherwise, an empty string. The default is an empty string.
/// </value>
protected override string ElementName
{
get { return "cache"; }
}
/// <summary>
/// Gets or sets the <see cref="CacheElement"/>
/// at the specified index within the collection.
/// </summary>
/// <param name="index">The index at which to get the specified object.</param>
/// <returns>
/// The <see cref="CacheElement"/>
/// at the specified index within the collection.
/// </returns>
public CacheElement this[int index]
{
get
{
return (CacheElement)BaseGet(index);
}
set
{
if (this.BaseGet(index) != null)
{
this.BaseRemoveAt(index);
}
this.BaseAdd(index, value);
}
}
/// <summary>
/// When overridden in a derived class, creates a new <see cref="ConfigurationElement"/>.
/// </summary>
/// <returns>
/// A new <see cref="ConfigurationElement"/>.
/// </returns>
protected override ConfigurationElement CreateNewElement()
{
return new CacheElement();
}
/// <summary>
/// Gets the element key for a specified configuration element when overridden in a derived class.
/// </summary>
/// <returns>
/// An <see cref="T:System.Object"/> that acts as the key for the specified <see cref="ConfigurationElement"/>.
/// </returns>
/// <param name="element">The <see cref="ConfigurationElement"/> to return the key for. </param>
protected override object GetElementKey(ConfigurationElement element)
{
return ((CacheElement)element).Name;
}
}
}
}

156
src/ImageProcessor.Web/Configuration/ImageProcessingSection.cs

@ -131,10 +131,10 @@ namespace ImageProcessor.Web.Configuration
public class PresetElementCollection : ConfigurationElementCollection
{
/// <summary>
/// Gets the type of the <see cref="T:System.Configuration.ConfigurationElementCollection"/>.
/// Gets the type of the <see cref="ConfigurationElementCollection"/>.
/// </summary>
/// <value>
/// The <see cref="T:System.Configuration.ConfigurationElementCollectionType"/> of this collection.
/// The <see cref="ConfigurationElementCollectionType"/> of this collection.
/// </value>
public override ConfigurationElementCollectionType CollectionType
{
@ -194,7 +194,7 @@ namespace ImageProcessor.Web.Configuration
/// Gets the element key for a specified PluginElement configuration element.
/// </summary>
/// <param name="element">
/// The <see cref="T:System.Configuration.ConfigurationElement">ConfigurationElement</see>
/// The <see cref="ConfigurationElement">ConfigurationElement</see>
/// to return the key for.
/// </param>
/// <returns>The element key for a specified PluginElement configuration element.</returns>
@ -255,10 +255,10 @@ namespace ImageProcessor.Web.Configuration
public class PluginElementCollection : ConfigurationElementCollection
{
/// <summary>
/// Gets the type of the <see cref="T:System.Configuration.ConfigurationElementCollection"/>.
/// Gets the type of the <see cref="ConfigurationElementCollection"/>.
/// </summary>
/// <value>
/// The <see cref="T:System.Configuration.ConfigurationElementCollectionType"/> of this collection.
/// The <see cref="ConfigurationElementCollectionType"/> of this collection.
/// </value>
public override ConfigurationElementCollectionType CollectionType
{
@ -318,7 +318,7 @@ namespace ImageProcessor.Web.Configuration
/// Gets the element key for a specified PluginElement configuration element.
/// </summary>
/// <param name="element">
/// The <see cref="T:System.Configuration.ConfigurationElement">ConfigurationElement</see>
/// The <see cref="ConfigurationElement">ConfigurationElement</see>
/// to return the key for.
/// </param>
/// <returns>The element key for a specified PluginElement configuration element.</returns>
@ -327,149 +327,5 @@ namespace ImageProcessor.Web.Configuration
return ((PluginElement)element).Name;
}
}
/// <summary>
/// Represents a SettingElement configuration element within the configuration.
/// </summary>
public class SettingElement : ConfigurationElement
{
/// <summary>
/// Gets or sets the key of the plugin setting.
/// </summary>
/// <value>The key of the plugin setting.</value>
[ConfigurationProperty("key", IsRequired = true, IsKey = true)]
public string Key
{
get
{
return this["key"] as string;
}
set
{
this["key"] = value;
}
}
/// <summary>
/// Gets or sets the value of the plugin setting.
/// </summary>
/// <value>The value of the plugin setting.</value>
[ConfigurationProperty("value", IsRequired = true)]
public string Value
{
get
{
return (string)this["value"];
}
set
{
this["value"] = value;
}
}
}
/// <summary>
/// Represents a SettingElementCollection collection configuration element within the configuration.
/// </summary>
public class SettingElementCollection : ConfigurationElementCollection
{
/// <summary>
/// Gets the type of the <see cref="T:System.Configuration.ConfigurationElementCollection"/>.
/// </summary>
/// <value>
/// The <see cref="T:System.Configuration.ConfigurationElementCollectionType"/> of this collection.
/// </value>
public override ConfigurationElementCollectionType CollectionType
{
get { return ConfigurationElementCollectionType.BasicMap; }
}
/// <summary>
/// Gets the name used to identify this collection of elements in the configuration file when overridden in a derived class.
/// </summary>
/// <value>
/// The name of the collection; otherwise, an empty string. The default is an empty string.
/// </value>
protected override string ElementName
{
get { return "setting"; }
}
/// <summary>
/// Gets or sets the <see cref="T:ImageProcessor.Web.Config.ImageProcessingSection.SettingElement"/>
/// at the specified index within the collection.
/// </summary>
/// <param name="index">The index at which to get the specified object.</param>
/// <returns>
/// The <see cref="T:ImageProcessor.Web.Config.ImageProcessingSection.SettingElement"/>
/// at the specified index within the collection.
/// </returns>
public SettingElement this[int index]
{
get
{
return (SettingElement)BaseGet(index);
}
set
{
if (this.BaseGet(index) != null)
{
this.BaseRemoveAt(index);
}
this.BaseAdd(index, value);
}
}
/// <summary>
/// Returns the setting element with the specified key.
/// </summary>
/// <param name="key">the key representing the element</param>
/// <returns>the setting element</returns>
public new SettingElement this[string key]
{
get { return (SettingElement)BaseGet(key); }
}
/// <summary>
/// Returns a value indicating whether the settings collection contains the
/// given object.
/// </summary>
/// <param name="key">The key to identify the setting.</param>
/// <returns>True if the collection contains the key; otherwise false.</returns>
public bool ContainsKey(string key)
{
object[] keys = BaseGetAllKeys();
return keys.Any(obj => (string)obj == key);
}
/// <summary>
/// Gets the element key for a specified PluginElement configuration element.
/// </summary>
/// <param name="element">
/// The <see cref="T:System.Configuration.ConfigurationElement">ConfigurationElement</see>
/// to return the key for.
/// </param>
/// <returns>The element key for a specified PluginElement configuration element.</returns>
protected override object GetElementKey(ConfigurationElement element)
{
return ((SettingElement)element).Key;
}
/// <summary>
/// Creates a new SettingElement configuration element.
/// </summary>
/// <returns>
/// A new SettingElement configuration element.
/// </returns>
protected override ConfigurationElement CreateNewElement()
{
return new SettingElement();
}
}
}
}

72
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<IImageService> ImageServices { get; private set; }
/// <summary>
/// Gets a value indicating whether to preserve exif meta data.
/// Gets the current image cache.
/// </summary>
public bool PreserveExifMetaData
{
get
{
return GetImageProcessingSection().PreserveExifMetaData;
}
}
public Type ImageCache { get; private set; }
#region Caching
/// <summary>
/// Gets the maximum number of days to store images in the cache.
/// Gets the image cache settings.
/// </summary>
public int MaxCacheDays
{
get
{
return GetImageCacheSection().MaxDays;
}
}
public Dictionary<string, string> ImageCacheSettings { get; private set; }
/// <summary>
/// Gets or the virtual path of the cache folder.
/// Gets a value indicating whether to preserve exif meta data.
/// </summary>
/// <value>The virtual path of the cache folder.</value>
public string VirtualCachePath
public bool PreserveExifMetaData
{
get
{
return GetImageCacheSection().VirtualPath;
return GetImageProcessingSection().PreserveExifMetaData;
}
}
#endregion
#endregion
#region Methods
/// <summary>
@ -271,7 +258,7 @@ namespace ImageProcessor.Web.Configuration
if (pluginElement != null)
{
settings = pluginElement.Settings
.Cast<ImageProcessingSection.SettingElement>()
.Cast<SettingElement>()
.ToDictionary(setting => setting.Key, setting => setting.Value);
}
else
@ -367,13 +354,13 @@ namespace ImageProcessor.Web.Configuration
}
/// <summary>
/// Returns the <see cref="T:ImageProcessor.Web.Config.ImageSecuritySection.SettingElementCollection"/> for the given plugin.
/// Returns the <see cref="SettingElementCollection"/> for the given plugin.
/// </summary>
/// <param name="name">
/// The name of the plugin to get the settings for.
/// </param>
/// <returns>
/// The <see cref="T:ImageProcessor.Web.Config.ImageSecuritySection.SettingElementCollection"/> for the given plugin.
/// The <see cref="SettingElementCollection"/> for the given plugin.
/// </returns>
private Dictionary<string, string> GetServiceSettings(string name)
{
@ -387,7 +374,7 @@ namespace ImageProcessor.Web.Configuration
if (serviceElement != null)
{
settings = serviceElement.Settings
.Cast<ImageSecuritySection.SettingElement>()
.Cast<SettingElement>()
.ToDictionary(setting => setting.Key, setting => setting.Value);
}
else
@ -424,6 +411,39 @@ namespace ImageProcessor.Web.Configuration
return whitelist;
}
#endregion
#region ImageCaches
/// <summary>
/// Gets the currently assigned <see cref="IImageCache"/>.
/// </summary>
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<SettingElement>()
.ToDictionary(setting => setting.Key, setting => setting.Value);
break;
}
}
}
}
#endregion
#endregion
}
}

171
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
/// </summary>
public sealed class ImageSecuritySection : ConfigurationSection
{
#region Properties
/// <summary>
/// Gets the <see cref="T:ImageProcessor.Web.Config.ImageSecuritySection.ServiceElementCollection"/>
/// Gets the <see cref="ServiceElementCollection"/>
/// </summary>
/// <value>The <see cref="T:ImageProcessor.Web.Config.ImageSecuritySection.ServiceElementCollection"/></value>
/// <value>The <see cref="ServiceElementCollection"/></value>
[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.
/// </summary>
public bool AutoLoadServices { get; set; }
#endregion
#region Methods
/// <summary>
/// Retrieves the security configuration section from the current application configuration.
/// </summary>
@ -66,7 +62,6 @@ namespace ImageProcessor.Web.Configuration
imageSecuritySection.AutoLoadServices = true;
return imageSecuritySection;
}
#endregion
/// <summary>
/// Represents a ServiceElement configuration element within the configuration.
@ -110,10 +105,10 @@ namespace ImageProcessor.Web.Configuration
}
/// <summary>
/// Gets the <see cref="T:ImageProcessor.Web.Config.ImageSecuritySection.SettingElementCollection"/>.
/// Gets the <see cref="SettingElementCollection"/>.
/// </summary>
/// <value>
/// The <see cref="T:ImageProcessor.Web.Config.ImageSecuritySection.SettingElementCollection"/>.
/// The <see cref="SettingElementCollection"/>.
/// </value>
[ConfigurationProperty("settings", IsRequired = false)]
public SettingElementCollection Settings
@ -146,10 +141,10 @@ namespace ImageProcessor.Web.Configuration
public class ServiceElementCollection : ConfigurationElementCollection
{
/// <summary>
/// Gets the type of the <see cref="T:System.Configuration.ConfigurationElementCollection"/>.
/// Gets the type of the <see cref="ConfigurationElementCollection"/>.
/// </summary>
/// <value>
/// The <see cref="T:System.Configuration.ConfigurationElementCollectionType"/> of this collection.
/// The <see cref="ConfigurationElementCollectionType"/> of this collection.
/// </value>
public override ConfigurationElementCollectionType CollectionType
{
@ -195,10 +190,10 @@ namespace ImageProcessor.Web.Configuration
}
/// <summary>
/// When overridden in a derived class, creates a new <see cref="T:System.Configuration.ConfigurationElement"/>.
/// When overridden in a derived class, creates a new <see cref="ConfigurationElement"/>.
/// </summary>
/// <returns>
/// A new <see cref="T:System.Configuration.ConfigurationElement"/>.
/// A new <see cref="ConfigurationElement"/>.
/// </returns>
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.
/// </summary>
/// <returns>
/// An <see cref="T:System.Object"/> that acts as the key for the specified <see cref="T:System.Configuration.ConfigurationElement"/>.
/// An <see cref="T:System.Object"/> that acts as the key for the specified <see cref="ConfigurationElement"/>.
/// </returns>
/// <param name="element">The <see cref="T:System.Configuration.ConfigurationElement"/> to return the key for. </param>
/// <param name="element">The <see cref="ConfigurationElement"/> to return the key for. </param>
protected override object GetElementKey(ConfigurationElement element)
{
return ((ServiceElement)element).Name;
}
}
/// <summary>
/// Represents a SettingElement configuration element within the configuration.
/// </summary>
public class SettingElement : ConfigurationElement
{
/// <summary>
/// Gets or sets the key of the plugin setting.
/// </summary>
/// <value>The key of the plugin setting.</value>
[ConfigurationProperty("key", IsRequired = true, IsKey = true)]
public string Key
{
get
{
return this["key"] as string;
}
set
{
this["key"] = value;
}
}
/// <summary>
/// Gets or sets the value of the plugin setting.
/// </summary>
/// <value>The value of the plugin setting.</value>
[ConfigurationProperty("value", IsRequired = true)]
public string Value
{
get
{
return (string)this["value"];
}
set
{
this["value"] = value;
}
}
}
/// <summary>
/// Represents a SettingElementCollection collection configuration element within the configuration.
/// </summary>
public class SettingElementCollection : ConfigurationElementCollection
{
/// <summary>
/// Gets the type of the <see cref="T:System.Configuration.ConfigurationElementCollection"/>.
/// </summary>
/// <value>
/// The <see cref="T:System.Configuration.ConfigurationElementCollectionType"/> of this collection.
/// </value>
public override ConfigurationElementCollectionType CollectionType
{
get { return ConfigurationElementCollectionType.BasicMap; }
}
/// <summary>
/// Gets the name used to identify this collection of elements in the configuration file when overridden in a derived class.
/// </summary>
/// <value>
/// The name of the collection; otherwise, an empty string. The default is an empty string.
/// </value>
protected override string ElementName
{
get { return "setting"; }
}
/// <summary>
/// Gets or sets the <see cref="T:ImageProcessor.Web.Config.ImageSecuritySection.SettingElement"/>
/// at the specified index within the collection.
/// </summary>
/// <param name="index">The index at which to get the specified object.</param>
/// <returns>
/// The <see cref="T:ImageProcessor.Web.Config.ImageSecuritySection.SettingElement"/>
/// at the specified index within the collection.
/// </returns>
public SettingElement this[int index]
{
get
{
return (SettingElement)BaseGet(index);
}
set
{
if (this.BaseGet(index) != null)
{
this.BaseRemoveAt(index);
}
this.BaseAdd(index, value);
}
}
/// <summary>
/// Returns the setting element with the specified key.
/// </summary>
/// <param name="key">the key representing the element</param>
/// <returns>the setting element</returns>
public new SettingElement this[string key]
{
get { return (SettingElement)BaseGet(key); }
}
/// <summary>
/// Returns a value indicating whether the settings collection contains the
/// given object.
/// </summary>
/// <param name="key">The key to identify the setting.</param>
/// <returns>True if the collection contains the key; otherwise false.</returns>
public bool ContainsKey(string key)
{
object[] keys = BaseGetAllKeys();
return keys.Any(obj => (string)obj == key);
}
/// <summary>
/// Gets the element key for a specified PluginElement configuration element.
/// </summary>
/// <param name="element">
/// The <see cref="T:System.Configuration.ConfigurationElement">ConfigurationElement</see>
/// to return the key for.
/// </param>
/// <returns>The element key for a specified PluginElement configuration element.</returns>
protected override object GetElementKey(ConfigurationElement element)
{
return ((SettingElement)element).Key;
}
/// <summary>
/// Creates a new SettingElement configuration element.
/// </summary>
/// <returns>
/// A new SettingElement configuration element.
/// </returns>
protected override ConfigurationElement CreateNewElement()
{
return new SettingElement();
}
}
/// <summary>
/// Represents a whitelist collection configuration element within the configuration.
/// </summary>
@ -404,7 +255,7 @@ namespace ImageProcessor.Web.Configuration
/// <summary>
/// Gets the element key for a specified whitelist configuration element.
/// </summary>
/// <param name="element">The <see cref="T:System.Configuration.ConfigurationElement">ConfigurationElement</see> to return the key for.</param>
/// <param name="element">The <see cref="ConfigurationElement">ConfigurationElement</see> to return the key for.</param>
/// <returns>The element key for a specified whitelist configuration element.</returns>
protected override object GetElementKey(ConfigurationElement element)
{

11
src/ImageProcessor.Web/Configuration/Resources/cache.config

@ -1 +1,10 @@
<cache virtualPath="~/app_data/cache" maxDays="365"/>
<caching currentCache="DiskCache">
<caches>
<cache name="DiskCache" type="ImageProcessor.Web.Caching.DiskCache, ImageProcessor.Web">
<settings>
<setting key="MaxAge" value="365"/>
<setting key="VirtualCachePath" value="~/app_data/cache"/>
</settings>
</cache>
</caches>
</caching>

56
src/ImageProcessor.Web/Configuration/Shared/SettingElement.cs

@ -0,0 +1,56 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="SettingElement.cs" company="James South">
// Copyright (c) James South.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// <summary>
// Represents a SettingElement configuration element within the configuration.
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace ImageProcessor.Web.Configuration
{
using System.Configuration;
/// <summary>
/// Represents a SettingElement configuration element within the configuration.
/// </summary>
public class SettingElement : ConfigurationElement
{
/// <summary>
/// Gets or sets the key of the plugin setting.
/// </summary>
/// <value>The key of the plugin setting.</value>
[ConfigurationProperty("key", IsRequired = true, IsKey = true)]
public string Key
{
get
{
return this["key"] as string;
}
set
{
this["key"] = value;
}
}
/// <summary>
/// Gets or sets the value of the plugin setting.
/// </summary>
/// <value>The value of the plugin setting.</value>
[ConfigurationProperty("value", IsRequired = true)]
public string Value
{
get
{
return (string)this["value"];
}
set
{
this["value"] = value;
}
}
}
}

117
src/ImageProcessor.Web/Configuration/Shared/SettingElementCollection.cs

@ -0,0 +1,117 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="SettingElementCollection.cs" company="James South">
// Copyright (c) James South.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// <summary>
// Represents a SettingElementCollection collection configuration element within the configuration.
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace ImageProcessor.Web.Configuration
{
using System.Configuration;
using System.Linq;
/// <summary>
/// Represents a SettingElementCollection collection configuration element within the configuration.
/// </summary>
public class SettingElementCollection : ConfigurationElementCollection
{
/// <summary>
/// Gets the type of the <see cref="ConfigurationElementCollection"/>.
/// </summary>
/// <value>
/// The <see cref="ConfigurationElementCollectionType"/> of this collection.
/// </value>
public override ConfigurationElementCollectionType CollectionType
{
get { return ConfigurationElementCollectionType.BasicMap; }
}
/// <summary>
/// Gets the name used to identify this collection of elements in the configuration file when overridden in a derived class.
/// </summary>
/// <value>
/// The name of the collection; otherwise, an empty string. The default is an empty string.
/// </value>
protected override string ElementName
{
get { return "setting"; }
}
/// <summary>
/// Gets or sets the <see cref="T:ImageProcessor.Web.Config.ImageSecuritySection.SettingElement"/>
/// at the specified index within the collection.
/// </summary>
/// <param name="index">The index at which to get the specified object.</param>
/// <returns>
/// The <see cref="T:ImageProcessor.Web.Config.ImageSecuritySection.SettingElement"/>
/// at the specified index within the collection.
/// </returns>
public SettingElement this[int index]
{
get
{
return (SettingElement)BaseGet(index);
}
set
{
if (this.BaseGet(index) != null)
{
this.BaseRemoveAt(index);
}
this.BaseAdd(index, value);
}
}
/// <summary>
/// Returns the setting element with the specified key.
/// </summary>
/// <param name="key">the key representing the element</param>
/// <returns>the setting element</returns>
public new SettingElement this[string key]
{
get { return (SettingElement)BaseGet(key); }
}
/// <summary>
/// Returns a value indicating whether the settings collection contains the
/// given object.
/// </summary>
/// <param name="key">The key to identify the setting.</param>
/// <returns>True if the collection contains the key; otherwise false.</returns>
public bool ContainsKey(string key)
{
object[] keys = BaseGetAllKeys();
return keys.Any(obj => (string)obj == key);
}
/// <summary>
/// Gets the element key for a specified PluginElement configuration element.
/// </summary>
/// <param name="element">
/// The <see cref="ConfigurationElement">ConfigurationElement</see>
/// to return the key for.
/// </param>
/// <returns>The element key for a specified PluginElement configuration element.</returns>
protected override object GetElementKey(ConfigurationElement element)
{
return ((SettingElement)element).Key;
}
/// <summary>
/// Creates a new SettingElement configuration element.
/// </summary>
/// <returns>
/// A new SettingElement configuration element.
/// </returns>
protected override ConfigurationElement CreateNewElement()
{
return new SettingElement();
}
}
}

180
src/ImageProcessor.Web/Extensions/TypeInitializationExtensions.cs

@ -0,0 +1,180 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="TypeInitializationExtensions.cs" company="James South">
// Copyright (c) James South.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// <summary>
// Extensions methods for <see cref="T:System.Type" /> for creating instances of types faster than
// using reflection. Modified from the original class at.
// <see href="http://geekswithblogs.net/mrsteve/archive/2012/02/19/a-fast-c-sharp-extension-method-using-expression-trees-create-instance-from-type-again.aspx" />
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace ImageProcessor.Web.Extensions
{
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
/// <summary>
/// Extensions methods for <see cref="T:System.Type"/> for creating instances of types faster than
/// using reflection. Modified from the original class at.
/// <see href="http://geekswithblogs.net/mrsteve/archive/2012/02/19/a-fast-c-sharp-extension-method-using-expression-trees-create-instance-from-type-again.aspx"/>
/// </summary>
internal static class TypeInitializationExtensions
{
/// <summary>
/// Returns an instance of the <paramref name="type"/> on which the method is invoked.
/// </summary>
/// <param name="type">The type on which the method was invoked.</param>
/// <returns>An instance of the <paramref name="type"/>.</returns>
public static object GetInstance(this Type type)
{
// This is about as quick as it gets.
return Activator.CreateInstance(type);
}
/// <summary>
/// Returns an instance of the <paramref name="type"/> on which the method is invoked.
/// </summary>
/// <typeparam name="TArg">The type of the argument to pass to the constructor.</typeparam>
/// <param name="type">The type on which the method was invoked.</param>
/// <param name="argument">The argument to pass to the constructor.</param>
/// <returns>An instance of the given <paramref name="type"/>.</returns>
public static object GetInstance<TArg>(this Type type, TArg argument)
{
return GetInstance<TArg, TypeToIgnore>(type, argument, null);
}
/// <summary>
/// Returns an instance of the <paramref name="type"/> on which the method is invoked.
/// </summary>
/// <typeparam name="TArg1">The type of the first argument to pass to the constructor.</typeparam>
/// <typeparam name="TArg2">The type of the second argument to pass to the constructor.</typeparam>
/// <param name="type">The type on which the method was invoked.</param>
/// <param name="argument1">The first argument to pass to the constructor.</param>
/// <param name="argument2">The second argument to pass to the constructor.</param>
/// <returns>An instance of the given <paramref name="type"/>.</returns>
public static object GetInstance<TArg1, TArg2>(this Type type, TArg1 argument1, TArg2 argument2)
{
return GetInstance<TArg1, TArg2, TypeToIgnore>(type, argument1, argument2, null);
}
/// <summary>
/// Returns an instance of the <paramref name="type"/> on which the method is invoked.
/// </summary>
/// <typeparam name="TArg1">The type of the first argument to pass to the constructor.</typeparam>
/// <typeparam name="TArg2">The type of the second argument to pass to the constructor.</typeparam>
/// <typeparam name="TArg3">The type of the third argument to pass to the constructor.</typeparam>
/// <param name="type">The type on which the method was invoked.</param>
/// <param name="argument1">The first argument to pass to the constructor.</param>
/// <param name="argument2">The second argument to pass to the constructor.</param>
/// <param name="argument3">The third argument to pass to the constructor.</param>
/// <returns>An instance of the given <paramref name="type"/>.</returns>
public static object GetInstance<TArg1, TArg2, TArg3>(
this Type type,
TArg1 argument1,
TArg2 argument2,
TArg3 argument3)
{
return InstanceCreationFactory<TArg1, TArg2, TArg3>
.CreateInstanceOf(type, argument1, argument2, argument3);
}
/// <summary>
/// The instance creation factory for creating instances.
/// </summary>
/// <typeparam name="TArg1">The type of the first argument to pass to the constructor.</typeparam>
/// <typeparam name="TArg2">The type of the second argument to pass to the constructor.</typeparam>
/// <typeparam name="TArg3">The type of the third argument to pass to the constructor.</typeparam>
private static class InstanceCreationFactory<TArg1, TArg2, TArg3>
{
/// <summary>
/// This dictionary will hold a cache of object-creation functions, keyed by the Type to create:
/// </summary>
private static readonly ConcurrentDictionary<Type, Func<TArg1, TArg2, TArg3, object>> InstanceCreationMethods = new ConcurrentDictionary<Type, Func<TArg1, TArg2, TArg3, object>>();
/// <summary>
/// The create instance of.
/// </summary>
/// <param name="type">
/// The type.
/// </param>
/// <param name="arg1">The first argument to pass to the constructor.</param>
/// <param name="arg2">The second argument to pass to the constructor.</param>
/// <param name="arg3">The third argument to pass to the constructor.</param>
/// <returns>
/// The <see cref="object"/>.
/// </returns>
public static object CreateInstanceOf(Type type, TArg1 arg1, TArg2 arg2, TArg3 arg3)
{
CacheInstanceCreationMethodIfRequired(type);
return InstanceCreationMethods[type].Invoke(arg1, arg2, arg3);
}
/// <summary>
/// Caches the instance creation method.
/// </summary>
/// <param name="type">
/// The <see cref="Type"/> who's constructor to cache.
/// </param>
private static void CacheInstanceCreationMethodIfRequired(Type type)
{
// Bail out if we've already cached the instance creation method:
Func<TArg1, TArg2, TArg3, object> 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<Expression>());
// Compile the Expression into a Func which takes three arguments and returns the constructed object:
Func<TArg1, TArg2, TArg3, object> constructorCallingLambda =
Expression.Lambda<Func<TArg1, TArg2, TArg3, object>>(
constructorCallExpression,
lamdaParameterExpressions).Compile();
InstanceCreationMethods.TryAdd(type, constructorCallingLambda);
}
}
/// <summary>
/// To allow for overloads with differing numbers of arguments, we flag arguments which should be
/// ignored by using this Type:
/// </summary>
private class TypeToIgnore
{
}
}
}

19
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<T>(Expression<Func<T>> expression)
{
MemberExpression member = expression.Body as MemberExpression;
if (member != null)
{
return member.Member.Name;
}
throw new ArgumentException("expression");
}
}
}

38
src/ImageProcessor.Web/HttpModules/ImageProcessingModule.cs

@ -3,34 +3,24 @@
// Copyright (c) James South.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// <summary>
// Processes any image requests within the web application.
// </summary>
// --------------------------------------------------------------------------------------------------------------------
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
/// <summary>
/// Processes any image requests within the web application.
@ -38,6 +28,7 @@ namespace ImageProcessor.Web.HttpModules
public sealed class ImageProcessingModule : IHttpModule
{
#region Fields
/// <summary>
/// The key for storing the response type of the current image.
/// </summary>
@ -86,10 +77,14 @@ namespace ImageProcessor.Web.HttpModules
/// </remarks>
private bool isDisposed;
/// <summary>
/// The image cache.
/// </summary>
private IImageCache imageCache;
#endregion
#region Destructors
/// <summary>
/// Finalizes an instance of the <see cref="T:ImageProcessor.Web.HttpModules.ImageProcessingModule"/> class.
/// </summary>
@ -107,6 +102,7 @@ namespace ImageProcessor.Web.HttpModules
// readability and maintainability.
this.Dispose(false);
}
#endregion
/// <summary>
@ -132,6 +128,7 @@ namespace ImageProcessor.Web.HttpModules
public static event ProcessQuerystringEventHandler OnProcessQuerystring;
#region IHttpModule Members
/// <summary>
/// Initializes a module and prepares it to handle requests.
/// </summary>
@ -174,7 +171,9 @@ namespace ImageProcessor.Web.HttpModules
/// <summary>
/// Disposes the object and frees resources for the Garbage Collector.
/// </summary>
/// <param name="disposing">If true, the object gets disposed.</param>
/// <param name="disposing">
/// If true, the object gets disposed.
/// </param>
private void Dispose(bool disposing)
{
if (this.isDisposed)
@ -192,6 +191,7 @@ namespace ImageProcessor.Web.HttpModules
// Note disposing is done.
this.isDisposed = true;
}
#endregion
/// <summary>
@ -249,8 +249,12 @@ namespace ImageProcessor.Web.HttpModules
/// <summary>
/// Occurs just before ASP.NET send HttpHeaders to the client.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">An <see cref="T:System.EventArgs">EventArgs</see> that contains the event data.</param>
/// <param name="sender">
/// The source of the event.
/// </param>
/// <param name="e">
/// An <see cref="T:System.EventArgs">EventArgs</see> that contains the event data.
/// </param>
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
}
}

6
src/ImageProcessor.Web/ImageProcessor.Web.csproj

@ -46,9 +46,13 @@
</ItemGroup>
<ItemGroup>
<Compile Include="Caching\CachedImage.cs" />
<Compile Include="Caching\DiskCache2.cs" />
<Compile Include="Caching\DiskCache.cs" />
<Compile Include="Caching\IImageCache.cs" />
<Compile Include="Caching\ImageCacheBase.cs" />
<Compile Include="Configuration\Shared\SettingElement.cs" />
<Compile Include="Configuration\Shared\SettingElementCollection.cs" />
<Compile Include="Extensions\TypeInitializationExtensions.cs" />
<Compile Include="Helpers\TypePropertyHelpers.cs" />
<Compile Include="Helpers\ProcessQueryStringEventArgs.cs" />
<Compile Include="Processors\DetectEdges.cs" />
<Compile Include="Processors\EntropyCrop.cs" />

2
src/ImageProcessor.Web/ImageProcessor.Web.csproj.DotSettings

@ -0,0 +1,2 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=configuration_005Cshared/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

4
src/TestWebsites/MVC/Test_Website_MVC.csproj

@ -152,6 +152,10 @@
<Content Include="Views\Home\Index.cshtml" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\ImageProcessor.Web.AzureBlobCache\ImageProcessor.Web.AzureBlobCache.csproj">
<Project>{3c805e4c-d679-43f8-8c43-8909cdb4d4d7}</Project>
<Name>ImageProcessor.Web.AzureBlobCache</Name>
</ProjectReference>
<ProjectReference Include="..\..\ImageProcessor.Web.PostProcessor\ImageProcessor.Web.PostProcessor.csproj">
<Project>{55d08737-7d7e-4995-8892-bd9f944329e6}</Project>
<Name>ImageProcessor.Web.PostProcessor</Name>

12
src/TestWebsites/MVC/config/imageprocessor/cache.config

@ -1,3 +1,11 @@
<?xml version="1.0" encoding="utf-8" ?>
<cache virtualPath="~/app_data/cache" maxDays="56"/>
<caching currentCache="DiskCache">
<caches>
<cache name="DiskCache" type="ImageProcessor.Web.Caching.DiskCache, ImageProcessor.Web">
<settings>
<setting key="MaxAge" value="56"/>
<setting key="VirtualCachePath" value="~/app_data/cache"/>
</settings>
</cache>
</caches>
</caching>

Loading…
Cancel
Save