mirror of https://github.com/SixLabors/ImageSharp
Browse Source
Former-commit-id: 68a3393b7f2e8629a85286303632cd6e4bd5d1f2 Former-commit-id: a6bd2be80b37ac875947edfdbafaf21506208e65pull/17/head
45 changed files with 1725 additions and 763 deletions
@ -0,0 +1,346 @@ |
|||
// --------------------------------------------------------------------------------------------------------------------
|
|||
// <copyright file="AzureBlobCache.cs" company="James South">
|
|||
// Copyright (c) James South.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
// <summary>
|
|||
// Provides an <see cref="IImageCache" /> implementation that uses Azure blob storage.
|
|||
// The cache is self healing and cleaning.
|
|||
// </summary>
|
|||
// --------------------------------------------------------------------------------------------------------------------
|
|||
|
|||
namespace ImageProcessor.Web.Caching |
|||
{ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Globalization; |
|||
using System.IO; |
|||
using System.Linq; |
|||
using System.Net; |
|||
using System.Text.RegularExpressions; |
|||
using System.Threading.Tasks; |
|||
using System.Web; |
|||
|
|||
using ImageProcessor.Web.Extensions; |
|||
using ImageProcessor.Web.Helpers; |
|||
|
|||
using Microsoft.WindowsAzure.Storage; |
|||
using Microsoft.WindowsAzure.Storage.Blob; |
|||
|
|||
/// <summary>
|
|||
/// Provides an <see cref="IImageCache"/> implementation that uses Azure blob storage.
|
|||
/// The cache is self healing and cleaning.
|
|||
/// </summary>
|
|||
public class AzureBlobCache : ImageCacheBase |
|||
{ |
|||
/// <summary>
|
|||
/// The maximum number of days to store the image.
|
|||
/// </summary>
|
|||
private readonly int maxDays; |
|||
|
|||
/// <summary>
|
|||
/// The cloud cached blob container.
|
|||
/// </summary>
|
|||
private readonly CloudBlobContainer cloudCachedBlobContainer; |
|||
|
|||
/// <summary>
|
|||
/// The cloud source blob container.
|
|||
/// </summary>
|
|||
private readonly CloudBlobContainer cloudSourceBlobContainer; |
|||
|
|||
/// <summary>
|
|||
/// The cached root url for a content delivery network.
|
|||
/// </summary>
|
|||
private readonly string cachedCdnRoot; |
|||
|
|||
/// <summary>
|
|||
/// The cached rewrite path.
|
|||
/// </summary>
|
|||
private string cachedRewritePath; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="AzureBlobCache"/> class.
|
|||
/// </summary>
|
|||
/// <param name="requestPath">
|
|||
/// The request path for the image.
|
|||
/// </param>
|
|||
/// <param name="fullPath">
|
|||
/// The full path for the image.
|
|||
/// </param>
|
|||
/// <param name="querystring">
|
|||
/// The querystring containing instructions.
|
|||
/// </param>
|
|||
public AzureBlobCache(string requestPath, string fullPath, string querystring) |
|||
: base(requestPath, fullPath, querystring) |
|||
{ |
|||
this.maxDays = Convert.ToInt32(this.Settings["MaxDays"]); |
|||
|
|||
// Retrieve storage accounts from connection string.
|
|||
CloudStorageAccount cloudCachedStorageAccount = CloudStorageAccount.Parse(this.Settings["CachedStorageAccount"]); |
|||
CloudStorageAccount cloudSourceStorageAccount = CloudStorageAccount.Parse(this.Settings["SourceStorageAccount"]); |
|||
|
|||
// Create the blob clients.
|
|||
CloudBlobClient cloudCachedBlobClient = cloudCachedStorageAccount.CreateCloudBlobClient(); |
|||
CloudBlobClient cloudSourceBlobClient = cloudSourceStorageAccount.CreateCloudBlobClient(); |
|||
|
|||
// Retrieve references to a previously created containers.
|
|||
this.cloudCachedBlobContainer = cloudCachedBlobClient.GetContainerReference(this.Settings["CachedBlobContainer"]); |
|||
this.cloudSourceBlobContainer = cloudSourceBlobClient.GetContainerReference(this.Settings["SourceBlobContainer"]); |
|||
|
|||
this.cachedCdnRoot = this.Settings["CachedCDNRoot"]; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the maximum number of days to store the image.
|
|||
/// </summary>
|
|||
public override int MaxDays |
|||
{ |
|||
get |
|||
{ |
|||
return this.maxDays; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether the image is new or updated in an asynchronous manner.
|
|||
/// </summary>
|
|||
/// <returns>
|
|||
/// The asynchronous <see cref="Task"/> returning the value.
|
|||
/// </returns>
|
|||
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)); |
|||
this.CachedPath = Path.Combine(this.cloudCachedBlobContainer.Uri.ToString(), pathFromKey, cachedFileName).Replace(@"\", "/"); |
|||
this.cachedRewritePath = Path.Combine(this.cachedCdnRoot, this.cloudCachedBlobContainer.Name, pathFromKey, cachedFileName).Replace(@"\", "/"); |
|||
|
|||
bool isUpdated = false; |
|||
CachedImage cachedImage = CacheIndexer.GetValue(this.CachedPath); |
|||
|
|||
if (new Uri(this.CachedPath).IsFile) |
|||
{ |
|||
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) |
|||
{ |
|||
string blobPath = this.CachedPath.Substring(this.cloudCachedBlobContainer.Uri.ToString().Length + 1); |
|||
CloudBlockBlob blockBlob = this.cloudCachedBlobContainer.GetBlockBlobReference(blobPath); |
|||
|
|||
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(cachedImage.CreationTimeUtc)) |
|||
{ |
|||
CacheIndexer.Remove(this.CachedPath); |
|||
isUpdated = true; |
|||
} |
|||
} |
|||
|
|||
return isUpdated; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Adds the image to the cache in an asynchronous manner.
|
|||
/// </summary>
|
|||
/// <param name="stream">
|
|||
/// The stream containing the image data.
|
|||
/// </param>
|
|||
/// <param name="contentType">
|
|||
/// The content type of the image.
|
|||
/// </param>
|
|||
/// <returns>
|
|||
/// The <see cref="Task"/> representing an asynchronous operation.
|
|||
/// </returns>
|
|||
public override async Task AddImageToCacheAsync(Stream stream, string contentType) |
|||
{ |
|||
string blobPath = this.CachedPath.Substring(this.cloudCachedBlobContainer.Uri.ToString().Length + 1); |
|||
CloudBlockBlob blockBlob = this.cloudCachedBlobContainer.GetBlockBlobReference(blobPath); |
|||
|
|||
await blockBlob.UploadFromStreamAsync(stream); |
|||
|
|||
blockBlob.Properties.ContentType = contentType; |
|||
blockBlob.Properties.CacheControl = string.Format("public, max-age={0}", this.MaxDays * 86400); |
|||
await blockBlob.SetPropertiesAsync(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Trims the cache of any expired items in an asynchronous manner.
|
|||
/// </summary>
|
|||
/// <returns>
|
|||
/// The asynchronous <see cref="Task"/> representing an asynchronous operation.
|
|||
/// </returns>
|
|||
public override async Task TrimCacheAsync() |
|||
{ |
|||
Uri uri = new Uri(this.CachedPath); |
|||
string path = uri.GetLeftPart(UriPartial.Path).Substring(this.cloudCachedBlobContainer.Uri.ToString().Length + 1); |
|||
string directory = path.Substring(0, path.LastIndexOf('/')); |
|||
string parent = directory.Substring(0, directory.LastIndexOf('/')); |
|||
|
|||
BlobContinuationToken continuationToken = null; |
|||
List<IListBlobItem> results = new List<IListBlobItem>(); |
|||
|
|||
// Loop through the all the files in a non blocking fashion.
|
|||
do |
|||
{ |
|||
BlobResultSegment response = await this.cloudCachedBlobContainer |
|||
.ListBlobsSegmentedAsync(parent, true, BlobListingDetails.Metadata, 5000, continuationToken, null, null); |
|||
continuationToken = response.ContinuationToken; |
|||
results.AddRange(response.Results); |
|||
} |
|||
while (continuationToken != null); |
|||
|
|||
// Now leap through and delete.
|
|||
foreach (CloudBlockBlob blob in results |
|||
.Where((blobItem, type) => blobItem is CloudBlockBlob) |
|||
.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)) |
|||
{ |
|||
break; |
|||
} |
|||
|
|||
// Remove from the cache and delete each CachedImage.
|
|||
CacheIndexer.Remove(blob.Name); |
|||
await blob.DeleteAsync(); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets a string identifying the cached file name.
|
|||
/// </summary>
|
|||
/// <returns>
|
|||
/// The asynchronous <see cref="Task"/> returning the value.
|
|||
/// </returns>
|
|||
public override async Task<string> CreateCachedFileName() |
|||
{ |
|||
string streamHash = string.Empty; |
|||
|
|||
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); |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
Regex regex = new Regex("^http(s)?://"); |
|||
string container = regex.Replace(this.cloudSourceBlobContainer.Uri.ToString(), string.Empty); |
|||
string blobPath = regex.Replace(this.RequestPath, string.Empty); |
|||
blobPath = blobPath.Replace(container, string.Empty).TrimStart('/'); |
|||
CloudBlockBlob blockBlob = this.cloudSourceBlobContainer.GetBlockBlobReference(blobPath); |
|||
|
|||
if (await blockBlob.ExistsAsync()) |
|||
{ |
|||
// Pull the latest info.
|
|||
await blockBlob.FetchAttributesAsync(); |
|||
|
|||
if (blockBlob.Properties.LastModified.HasValue) |
|||
{ |
|||
string creation = blockBlob.Properties |
|||
.LastModified.Value.UtcDateTime |
|||
.ToString(CultureInfo.InvariantCulture); |
|||
|
|||
string length = blockBlob.Properties.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(); |
|||
|
|||
string cachedFileName = string.Format( |
|||
"{0}.{1}", |
|||
encryptedName, |
|||
!string.IsNullOrWhiteSpace(parsedExtension) ? parsedExtension.Replace(".", string.Empty) : "jpg"); |
|||
|
|||
return cachedFileName; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Rewrites the path to point to the cached image.
|
|||
/// </summary>
|
|||
/// <param name="context">
|
|||
/// The <see cref="HttpContext"/> encapsulating all information about the request.
|
|||
/// </param>
|
|||
public override void RewritePath(HttpContext context) |
|||
{ |
|||
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(this.cachedRewritePath); |
|||
request.Method = "HEAD"; |
|||
|
|||
using (HttpWebResponse response = (HttpWebResponse)request.GetResponse()) |
|||
{ |
|||
HttpStatusCode responseCode = response.StatusCode; |
|||
context.Response.Redirect( |
|||
responseCode == HttpStatusCode.NotFound ? this.CachedPath : this.cachedRewritePath, |
|||
false); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,99 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> |
|||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> |
|||
<PropertyGroup> |
|||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> |
|||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> |
|||
<ProjectGuid>{3C805E4C-D679-43F8-8C43-8909CDB4D4D7}</ProjectGuid> |
|||
<OutputType>Library</OutputType> |
|||
<AppDesignerFolder>Properties</AppDesignerFolder> |
|||
<RootNamespace>ImageProcessor.Web.Caching</RootNamespace> |
|||
<AssemblyName>ImageProcessor.Web.Caching.AzureBlobCache</AssemblyName> |
|||
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion> |
|||
<FileAlignment>512</FileAlignment> |
|||
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir> |
|||
<RestorePackages>true</RestorePackages> |
|||
</PropertyGroup> |
|||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> |
|||
<DebugSymbols>true</DebugSymbols> |
|||
<DebugType>full</DebugType> |
|||
<Optimize>false</Optimize> |
|||
<OutputPath>bin\Debug\</OutputPath> |
|||
<DefineConstants>DEBUG;TRACE</DefineConstants> |
|||
<ErrorReport>prompt</ErrorReport> |
|||
<WarningLevel>4</WarningLevel> |
|||
</PropertyGroup> |
|||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> |
|||
<DebugType>pdbonly</DebugType> |
|||
<Optimize>true</Optimize> |
|||
<OutputPath>bin\Release\</OutputPath> |
|||
<DefineConstants>TRACE</DefineConstants> |
|||
<ErrorReport>prompt</ErrorReport> |
|||
<WarningLevel>4</WarningLevel> |
|||
</PropertyGroup> |
|||
<ItemGroup> |
|||
<Reference Include="Microsoft.Data.Edm"> |
|||
<HintPath>..\packages\Microsoft.Data.Edm.5.6.2\lib\net40\Microsoft.Data.Edm.dll</HintPath> |
|||
<Private>True</Private> |
|||
</Reference> |
|||
<Reference Include="Microsoft.Data.OData"> |
|||
<HintPath>..\packages\Microsoft.Data.OData.5.6.2\lib\net40\Microsoft.Data.OData.dll</HintPath> |
|||
<Private>True</Private> |
|||
</Reference> |
|||
<Reference Include="Microsoft.Data.Services.Client, Version=5.6.2.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"> |
|||
<SpecificVersion>False</SpecificVersion> |
|||
<HintPath>..\packages\Microsoft.Data.Services.Client.5.6.2\lib\net40\Microsoft.Data.Services.Client.dll</HintPath> |
|||
</Reference> |
|||
<Reference Include="Microsoft.WindowsAzure.Configuration"> |
|||
<HintPath>..\packages\Microsoft.WindowsAzure.ConfigurationManager.1.8.0.0\lib\net35-full\Microsoft.WindowsAzure.Configuration.dll</HintPath> |
|||
</Reference> |
|||
<Reference Include="Microsoft.WindowsAzure.Storage"> |
|||
<HintPath>..\packages\WindowsAzure.Storage.4.3.0\lib\net40\Microsoft.WindowsAzure.Storage.dll</HintPath> |
|||
</Reference> |
|||
<Reference Include="Newtonsoft.Json"> |
|||
<HintPath>..\packages\Newtonsoft.Json.5.0.8\lib\net45\Newtonsoft.Json.dll</HintPath> |
|||
<Private>True</Private> |
|||
</Reference> |
|||
<Reference Include="System" /> |
|||
<Reference Include="System.Configuration" /> |
|||
<Reference Include="System.Core" /> |
|||
<Reference Include="System.Spatial, Version=5.6.2.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"> |
|||
<SpecificVersion>False</SpecificVersion> |
|||
<HintPath>..\packages\System.Spatial.5.6.2\lib\net40\System.Spatial.dll</HintPath> |
|||
</Reference> |
|||
<Reference Include="System.Web" /> |
|||
<Reference Include="System.Xml.Linq" /> |
|||
<Reference Include="System.Data.DataSetExtensions" /> |
|||
<Reference Include="Microsoft.CSharp" /> |
|||
<Reference Include="System.Data" /> |
|||
<Reference Include="System.Xml" /> |
|||
</ItemGroup> |
|||
<ItemGroup> |
|||
<Compile Include="AzureBlobCache.cs" /> |
|||
<Compile Include="Properties\AssemblyInfo.cs" /> |
|||
</ItemGroup> |
|||
<ItemGroup> |
|||
<ProjectReference Include="..\ImageProcessor.Web\ImageProcessor.Web.csproj"> |
|||
<Project>{d011a778-59c8-4bfa-a770-c350216bf161}</Project> |
|||
<Name>ImageProcessor.Web</Name> |
|||
</ProjectReference> |
|||
</ItemGroup> |
|||
<ItemGroup> |
|||
<None Include="packages.config" /> |
|||
</ItemGroup> |
|||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> |
|||
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" /> |
|||
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild"> |
|||
<PropertyGroup> |
|||
<ErrorText>This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText> |
|||
</PropertyGroup> |
|||
<Error Condition="!Exists('$(SolutionDir)\.nuget\NuGet.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(SolutionDir)\.nuget\NuGet.targets'))" /> |
|||
</Target> |
|||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it. |
|||
Other similar extension points exist, see Microsoft.Common.targets. |
|||
<Target Name="BeforeBuild"> |
|||
</Target> |
|||
<Target Name="AfterBuild"> |
|||
</Target> |
|||
--> |
|||
</Project> |
|||
@ -0,0 +1,36 @@ |
|||
using System.Reflection; |
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.InteropServices; |
|||
|
|||
// General Information about an assembly is controlled through the following
|
|||
// set of attributes. Change these attribute values to modify the information
|
|||
// associated with an assembly.
|
|||
[assembly: AssemblyTitle("ImageProcessor.Web.AzureBlobCache")] |
|||
[assembly: AssemblyDescription("")] |
|||
[assembly: AssemblyConfiguration("")] |
|||
[assembly: AssemblyCompany("")] |
|||
[assembly: AssemblyProduct("ImageProcessor.Web.AzureBlobCache")] |
|||
[assembly: AssemblyCopyright("Copyright © 2015")] |
|||
[assembly: AssemblyTrademark("")] |
|||
[assembly: AssemblyCulture("")] |
|||
|
|||
// Setting ComVisible to false makes the types in this assembly not visible
|
|||
// to COM components. If you need to access a type in this assembly from
|
|||
// COM, set the ComVisible attribute to true on that type.
|
|||
[assembly: ComVisible(false)] |
|||
|
|||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
|||
[assembly: Guid("90605e94-25f4-4c69-b602-6b1df0adb89e")] |
|||
|
|||
// Version information for an assembly consists of the following four values:
|
|||
//
|
|||
// Major Version
|
|||
// Minor Version
|
|||
// Build Number
|
|||
// Revision
|
|||
//
|
|||
// You can specify all the values or you can default the Build and Revision Numbers
|
|||
// by using the '*' as shown below:
|
|||
// [assembly: AssemblyVersion("1.0.*")]
|
|||
[assembly: AssemblyVersion("1.0.0.0")] |
|||
[assembly: AssemblyFileVersion("1.0.0.0")] |
|||
@ -0,0 +1,10 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<packages> |
|||
<package id="Microsoft.Data.Edm" version="5.6.2" targetFramework="net45" /> |
|||
<package id="Microsoft.Data.OData" version="5.6.2" targetFramework="net45" /> |
|||
<package id="Microsoft.Data.Services.Client" version="5.6.2" targetFramework="net45" /> |
|||
<package id="Microsoft.WindowsAzure.ConfigurationManager" version="1.8.0.0" targetFramework="net45" /> |
|||
<package id="Newtonsoft.Json" version="5.0.8" targetFramework="net45" /> |
|||
<package id="System.Spatial" version="5.6.2" targetFramework="net45" /> |
|||
<package id="WindowsAzure.Storage" version="4.3.0" targetFramework="net45" /> |
|||
</packages> |
|||
@ -1,13 +1,13 @@ |
|||
#Resource locations |
|||
|
|||
###gifsicle |
|||
http://www.lcdf.org/gifsicle/ |
|||
[http://www.lcdf.org/gifsicle/](http://www.lcdf.org/gifsicle/) |
|||
|
|||
###jpegtran |
|||
http://jpegclub.org/jpegtran/ |
|||
[http://jpegclub.org/jpegtran/](http://jpegclub.org/jpegtran/) |
|||
|
|||
###optipng |
|||
http://optipng.sourceforge.net/ |
|||
[http://optipng.sourceforge.net/](http://optipng.sourceforge.net/) |
|||
|
|||
###pngout |
|||
http://advsys.net/ken/utils.htm |
|||
[http://advsys.net/ken/utils.htm](http://advsys.net/ken/utils.htm) |
|||
@ -0,0 +1,84 @@ |
|||
// --------------------------------------------------------------------------------------------------------------------
|
|||
// <copyright file="IImageCache.cs" company="James South">
|
|||
// Copyright (c) James South.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
// <summary>
|
|||
// Defines properties and methods for allowing caching of images to different sources.
|
|||
// </summary>
|
|||
// --------------------------------------------------------------------------------------------------------------------
|
|||
|
|||
namespace ImageProcessor.Web.Caching |
|||
{ |
|||
using System.Collections.Generic; |
|||
using System.IO; |
|||
using System.Threading.Tasks; |
|||
using System.Web; |
|||
|
|||
/// <summary>
|
|||
/// Defines properties and methods for allowing caching of images to different sources.
|
|||
/// </summary>
|
|||
public interface IImageCache |
|||
{ |
|||
/// <summary>
|
|||
/// Gets or sets any additional settings required by the cache.
|
|||
/// </summary>
|
|||
Dictionary<string, string> Settings { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the path to the cached image.
|
|||
/// </summary>
|
|||
string CachedPath { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the maximum number of days to store the image.
|
|||
/// </summary>
|
|||
int MaxDays { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether the image is new or updated in an asynchronous manner.
|
|||
/// </summary>
|
|||
/// <returns>
|
|||
/// The asynchronous <see cref="Task"/> returning the value.
|
|||
/// </returns>
|
|||
Task<bool> IsNewOrUpdatedAsync(); |
|||
|
|||
/// <summary>
|
|||
/// Adds the image to the cache in an asynchronous manner.
|
|||
/// </summary>
|
|||
/// <param name="stream">
|
|||
/// The stream containing the image data.
|
|||
/// </param>
|
|||
/// <param name="contentType">
|
|||
/// The content type of the image.
|
|||
/// </param>
|
|||
/// <returns>
|
|||
/// The <see cref="Task"/> representing an asynchronous operation.
|
|||
/// </returns>
|
|||
Task AddImageToCacheAsync(Stream stream, string contentType); |
|||
|
|||
/// <summary>
|
|||
/// Trims the cache of any expired items in an asynchronous manner.
|
|||
/// </summary>
|
|||
/// <returns>
|
|||
/// The asynchronous <see cref="Task"/> representing an asynchronous operation.
|
|||
/// </returns>
|
|||
Task TrimCacheAsync(); |
|||
|
|||
/// <summary>
|
|||
/// Gets a string identifying the cached file name.
|
|||
/// </summary>
|
|||
/// <returns>
|
|||
/// The asynchronous <see cref="Task"/> returning the value.
|
|||
/// </returns>
|
|||
Task<string> CreateCachedFileName(); |
|||
|
|||
/// <summary>
|
|||
/// Rewrites the path to point to the cached image.
|
|||
/// </summary>
|
|||
/// <param name="context">
|
|||
/// The <see cref="HttpContext"/> encapsulating all information about the request.
|
|||
/// </param>
|
|||
void RewritePath(HttpContext context); |
|||
} |
|||
} |
|||
@ -0,0 +1,182 @@ |
|||
// --------------------------------------------------------------------------------------------------------------------
|
|||
// <copyright file="ImageCacheBase.cs" company="James South">
|
|||
// Copyright (c) James South.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
// <summary>
|
|||
// The image cache base provides methods for implementing the <see cref="IImageCache" /> interface.
|
|||
// It is recommended that any implementations inherit from this class.
|
|||
// </summary>
|
|||
// --------------------------------------------------------------------------------------------------------------------
|
|||
|
|||
namespace ImageProcessor.Web.Caching |
|||
{ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Globalization; |
|||
using System.IO; |
|||
using System.Threading.Tasks; |
|||
using System.Web; |
|||
|
|||
using ImageProcessor.Web.Configuration; |
|||
using ImageProcessor.Web.Extensions; |
|||
using ImageProcessor.Web.Helpers; |
|||
|
|||
/// <summary>
|
|||
/// The image cache base provides methods for implementing the <see cref="IImageCache"/> interface.
|
|||
/// It is recommended that any implementations inherit from this class.
|
|||
/// </summary>
|
|||
public abstract class ImageCacheBase : IImageCache |
|||
{ |
|||
/// <summary>
|
|||
/// The request path for the image.
|
|||
/// </summary>
|
|||
protected readonly string RequestPath; |
|||
|
|||
/// <summary>
|
|||
/// The full path for the image.
|
|||
/// </summary>
|
|||
protected readonly string FullPath; |
|||
|
|||
/// <summary>
|
|||
/// The querystring containing processing instructions.
|
|||
/// </summary>
|
|||
protected readonly string Querystring; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="ImageCacheBase"/> class.
|
|||
/// </summary>
|
|||
/// <param name="requestPath">
|
|||
/// The request path for the image.
|
|||
/// </param>
|
|||
/// <param name="fullPath">
|
|||
/// The full path for the image.
|
|||
/// </param>
|
|||
/// <param name="querystring">
|
|||
/// The querystring containing instructions.
|
|||
/// </param>
|
|||
protected ImageCacheBase(string requestPath, string fullPath, string querystring) |
|||
{ |
|||
this.RequestPath = requestPath; |
|||
this.FullPath = fullPath; |
|||
this.Querystring = querystring; |
|||
this.Settings = ImageProcessorConfiguration.Instance.ImageCacheSettings; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets any additional settings required by the cache.
|
|||
/// </summary>
|
|||
public Dictionary<string, string> Settings { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the path to the cached image.
|
|||
/// </summary>
|
|||
public string CachedPath { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the maximum number of days to store the image.
|
|||
/// </summary>
|
|||
public abstract int MaxDays { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether the image is new or updated in an asynchronous manner.
|
|||
/// </summary>
|
|||
/// <returns>
|
|||
/// The <see cref="Task"/>.
|
|||
/// </returns>
|
|||
public abstract Task<bool> IsNewOrUpdatedAsync(); |
|||
|
|||
/// <summary>
|
|||
/// Adds the image to the cache in an asynchronous manner.
|
|||
/// </summary>
|
|||
/// <param name="stream">
|
|||
/// The stream containing the image data.
|
|||
/// </param>
|
|||
/// <param name="contentType">
|
|||
/// The content type of the image.
|
|||
/// </param>
|
|||
/// <returns>
|
|||
/// The <see cref="Task"/> representing an asynchronous operation.
|
|||
/// </returns>
|
|||
public abstract Task AddImageToCacheAsync(Stream stream, string contentType); |
|||
|
|||
/// <summary>
|
|||
/// Trims the cache of any expired items in an asynchronous manner.
|
|||
/// </summary>
|
|||
/// <returns>
|
|||
/// The asynchronous <see cref="Task"/> representing an asynchronous operation.
|
|||
/// </returns>
|
|||
public abstract Task TrimCacheAsync(); |
|||
|
|||
/// <summary>
|
|||
/// Gets a string identifying the cached file name.
|
|||
/// </summary>
|
|||
/// <returns>
|
|||
/// The asynchronous <see cref="Task"/> returning the value.
|
|||
/// </returns>
|
|||
public virtual Task<string> CreateCachedFileName() |
|||
{ |
|||
string streamHash = string.Empty; |
|||
|
|||
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(); |
|||
|
|||
string cachedFileName = string.Format( |
|||
"{0}.{1}", |
|||
encryptedName, |
|||
!string.IsNullOrWhiteSpace(parsedExtension) ? parsedExtension.Replace(".", string.Empty) : "jpg"); |
|||
|
|||
return Task.FromResult(cachedFileName); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Rewrites the path to point to the cached image.
|
|||
/// </summary>
|
|||
/// <param name="context">
|
|||
/// The <see cref="HttpContext"/> encapsulating all information about the request.
|
|||
/// </param>
|
|||
public abstract void RewritePath(HttpContext context); |
|||
|
|||
/// <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>
|
|||
protected virtual bool IsExpired(DateTime creationDate) |
|||
{ |
|||
return creationDate.AddDays(this.MaxDays) < DateTime.UtcNow.AddDays(-this.MaxDays); |
|||
} |
|||
} |
|||
} |
|||
@ -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> |
|||
|
|||
@ -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; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -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(); |
|||
} |
|||
} |
|||
} |
|||
@ -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 |
|||
{ |
|||
} |
|||
} |
|||
} |
|||
@ -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"); |
|||
} |
|||
} |
|||
} |
|||
@ -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> |
|||
@ -1,6 +1,7 @@ |
|||
<StyleCopSettings Version="105"> |
|||
<GlobalSettings> |
|||
<CollectionProperty Name="RecognizedWords"> |
|||
<Value>cdn</Value> |
|||
<Value>dllimport</Value> |
|||
</CollectionProperty> |
|||
</GlobalSettings> |
|||
|
|||
@ -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="MaxDays" value="56"/> |
|||
<setting key="VirtualCachePath" value="~/app_data/cache"/> |
|||
</settings> |
|||
</cache> |
|||
</caches> |
|||
</caching> |
|||
|
|||
|
|||
@ -1,6 +1,7 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<repositories> |
|||
<repository path="..\ImageProcessor.UnitTests\packages.config" /> |
|||
<repository path="..\ImageProcessor.Web.AzureBlobCache\packages.config" /> |
|||
<repository path="..\ImageProcessor.Web.UnitTests\packages.config" /> |
|||
<repository path="..\TestWebsites\MVC\packages.config" /> |
|||
</repositories> |
|||
Loading…
Reference in new issue