mirror of https://github.com/SixLabors/ImageSharp
Browse Source
Former-commit-id: f93c3a6f44bfbd5cdb0ec4cc2234b99dfe631ee5 Former-commit-id: 11072be0a6633bc6ffad01caa095c007a4b2b453pull/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 |
#Resource locations |
||||
|
|
||||
###gifsicle |
###gifsicle |
||||
http://www.lcdf.org/gifsicle/ |
[http://www.lcdf.org/gifsicle/](http://www.lcdf.org/gifsicle/) |
||||
|
|
||||
###jpegtran |
###jpegtran |
||||
http://jpegclub.org/jpegtran/ |
[http://jpegclub.org/jpegtran/](http://jpegclub.org/jpegtran/) |
||||
|
|
||||
###optipng |
###optipng |
||||
http://optipng.sourceforge.net/ |
[http://optipng.sourceforge.net/](http://optipng.sourceforge.net/) |
||||
|
|
||||
###pngout |
###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"> |
<StyleCopSettings Version="105"> |
||||
<GlobalSettings> |
<GlobalSettings> |
||||
<CollectionProperty Name="RecognizedWords"> |
<CollectionProperty Name="RecognizedWords"> |
||||
|
<Value>cdn</Value> |
||||
<Value>dllimport</Value> |
<Value>dllimport</Value> |
||||
</CollectionProperty> |
</CollectionProperty> |
||||
</GlobalSettings> |
</GlobalSettings> |
||||
|
|||||
@ -1,3 +1,11 @@ |
|||||
<?xml version="1.0" encoding="utf-8" ?> |
<caching currentCache="DiskCache"> |
||||
<cache virtualPath="~/app_data/cache" maxDays="56"/> |
<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"?> |
<?xml version="1.0" encoding="utf-8"?> |
||||
<repositories> |
<repositories> |
||||
<repository path="..\ImageProcessor.UnitTests\packages.config" /> |
<repository path="..\ImageProcessor.UnitTests\packages.config" /> |
||||
|
<repository path="..\ImageProcessor.Web.AzureBlobCache\packages.config" /> |
||||
<repository path="..\ImageProcessor.Web.UnitTests\packages.config" /> |
<repository path="..\ImageProcessor.Web.UnitTests\packages.config" /> |
||||
<repository path="..\TestWebsites\MVC\packages.config" /> |
<repository path="..\TestWebsites\MVC\packages.config" /> |
||||
</repositories> |
</repositories> |
||||
Loading…
Reference in new issue