diff --git a/src/ImageProcessor.Web/Configuration/ImageProcessingSection.cs b/src/ImageProcessor.Web/Configuration/ImageProcessingSection.cs index 23f8bc9ca..6ece1681b 100644 --- a/src/ImageProcessor.Web/Configuration/ImageProcessingSection.cs +++ b/src/ImageProcessor.Web/Configuration/ImageProcessingSection.cs @@ -155,7 +155,7 @@ namespace ImageProcessor.Web.Configuration /// /// The index at which to get the specified object. /// - /// The the + /// The /// at the specified index within the collection. /// public PresetElement this[int index] diff --git a/src/ImageProcessor.Web/Configuration/ImageProcessorConfiguration.cs b/src/ImageProcessor.Web/Configuration/ImageProcessorConfiguration.cs index a9684c9c5..fde8deeb2 100644 --- a/src/ImageProcessor.Web/Configuration/ImageProcessorConfiguration.cs +++ b/src/ImageProcessor.Web/Configuration/ImageProcessorConfiguration.cs @@ -20,6 +20,7 @@ namespace ImageProcessor.Web.Configuration using ImageProcessor.Common.Extensions; using ImageProcessor.Processors; using ImageProcessor.Web.Processors; + using ImageProcessor.Web.Services; /// /// Encapsulates methods to allow the retrieval of ImageProcessor settings. @@ -72,6 +73,7 @@ namespace ImageProcessor.Web.Configuration private ImageProcessorConfiguration() { this.LoadGraphicsProcessors(); + this.LoadImageServices(); } #endregion @@ -92,6 +94,11 @@ namespace ImageProcessor.Web.Configuration /// public IList GraphicsProcessors { get; private set; } + /// + /// Gets the list of available ImageServices. + /// + public IList ImageServices { get; private set; } + /// /// Gets a value indicating whether to preserve exif meta data. /// @@ -136,7 +143,7 @@ namespace ImageProcessor.Web.Configuration { get { - return GetImageSecuritySection().WhiteList.Cast().Select(x => x.Url).ToArray(); + return GetImageSecuritySection().ImageServices.Cast().Select(x => x.Url).ToArray(); } } @@ -147,7 +154,7 @@ namespace ImageProcessor.Web.Configuration { get { - return GetImageSecuritySection().WhiteList.Cast().ToArray(); + return GetImageSecuritySection().ImageServices.Cast().ToArray(); } } @@ -258,6 +265,43 @@ namespace ImageProcessor.Web.Configuration }); } + /// + /// Returns the for the given plugin. + /// + /// + /// The name of the plugin to get the settings for. + /// + /// + /// The for the given plugin. + /// + public Dictionary GetServiceSettings(string name) + { + return PluginSettings.GetOrAdd( + name, + n => + { + ImageSecuritySection.ServiceElement pluginElement = GetImageSecuritySection() + .ImageServices + .Cast() + .FirstOrDefault(x => x.Name == n); + + Dictionary settings; + + if (pluginElement != null) + { + settings = pluginElement.Settings + .Cast() + .ToDictionary(setting => setting.Key, setting => setting.Value); + } + else + { + settings = new Dictionary(); + } + + return settings; + }); + } + /// /// Retrieves the processing configuration section from the current application configuration. /// @@ -325,11 +369,51 @@ namespace ImageProcessor.Web.Configuration } } + /// + /// Gets the list of available ImageServices. + /// + private void LoadImageServices() + { + if (this.ImageServices == null) + { + if (GetImageSecuritySection().ImageServices.AutoLoadPlugins) + { + Type type = typeof(IImageService); + try + { + // Build a list of native IGraphicsProcessor instances. + List availableTypes = BuildManager.GetReferencedAssemblies() + .Cast() + .SelectMany(s => s.GetLoadableTypes()) + .Where(t => type.IsAssignableFrom(t) && t.IsClass && !t.IsAbstract) + .ToList(); + + // Create them and add. + this.ImageServices = availableTypes.Select(x => (Activator.CreateInstance(x) as IImageService)).ToList(); + + // Add the available settings. + foreach (IImageService imageService in this.ImageServices) + { + imageService.Settings = this.GetServiceSettings(imageService.GetType().Name); + } + } + catch (ReflectionTypeLoadException) + { + this.LoadImageServicesFromConfiguration(); + } + } + else + { + this.LoadImageServicesFromConfiguration(); + } + } + } + /// /// Loads graphics processors from configuration. /// /// - /// Thrown when an cannot be loaded. + /// Thrown when an cannot be loaded. /// private void LoadGraphicsProcessorsFromConfiguration() { @@ -353,6 +437,35 @@ namespace ImageProcessor.Web.Configuration webProcessor.Processor.Settings = this.GetPluginSettings(webProcessor.GetType().Name); } } + + /// + /// Loads image services from configuration. + /// + /// + /// Thrown when an cannot be loaded. + /// + private void LoadImageServicesFromConfiguration() + { + ImageSecuritySection.ServiceElementCollection services = imageSecuritySection.ImageServices; + this.ImageServices = new List(); + foreach (ImageSecuritySection.ServiceElement config in services) + { + Type type = Type.GetType(config.Type); + + if (type == null) + { + throw new TypeLoadException("Couldn't load IImageService: " + config.Type); + } + + this.GraphicsProcessors.Add(Activator.CreateInstance(type) as IWebGraphicsProcessor); + } + + // Add the available settings. + foreach (IImageService service in this.ImageServices) + { + service.Settings = this.GetServiceSettings(service.GetType().Name); + } + } #endregion } } \ No newline at end of file diff --git a/src/ImageProcessor.Web/Configuration/ImageSecuritySection.cs b/src/ImageProcessor.Web/Configuration/ImageSecuritySection.cs index c58b07579..814fee0b0 100644 --- a/src/ImageProcessor.Web/Configuration/ImageSecuritySection.cs +++ b/src/ImageProcessor.Web/Configuration/ImageSecuritySection.cs @@ -10,16 +10,14 @@ namespace ImageProcessor.Web.Configuration { - #region Using using System; using System.Configuration; using System.IO; + using System.Linq; using System.Xml; using ImageProcessor.Web.Helpers; - #endregion - /// /// Represents an image security section within a configuration file. /// @@ -91,13 +89,13 @@ namespace ImageProcessor.Web.Configuration /// Gets the /// /// The - [ConfigurationProperty("whiteList", IsRequired = true)] - public WhiteListElementCollection WhiteList + [ConfigurationProperty("services", IsRequired = true)] + public ServiceElementCollection ImageServices { get { - object o = this["whiteList"]; - return o as WhiteListElementCollection; + object o = this["services"]; + return o as ServiceElementCollection; } } #endregion @@ -125,6 +123,301 @@ namespace ImageProcessor.Web.Configuration } #endregion + /// + /// Represents a ServiceElement configuration element within the configuration. + /// + public class ServiceElement : ConfigurationElement + { + /// + /// Gets or sets the name of the plugin file. + /// + /// The name of the plugin. + [ConfigurationProperty("name", DefaultValue = "", IsRequired = true)] + public string Name + { + get { return (string)this["name"]; } + + set { this["name"] = value; } + } + + /// + /// Gets or sets the type of the service file. + /// + /// The full Type definition of the plugin + [ConfigurationProperty("type", DefaultValue = "", IsRequired = true)] + public string Type + { + get { return (string)this["type"]; } + + set { this["type"] = value; } + } + + /// + /// Gets the . + /// + /// + /// The . + /// + [ConfigurationProperty("settings", IsRequired = false)] + public SettingElementCollection Settings + { + get + { + return this["settings"] as SettingElementCollection; + } + } + + /// + /// Gets the . + /// + /// + /// The . + /// + [ConfigurationProperty("whitelist", IsRequired = false)] + public WhiteListElementCollection WhiteList + { + get + { + return this["whitelist"] as WhiteListElementCollection; + } + } + } + + /// + /// Represents a collection of elements within the configuration. + /// + public class ServiceElementCollection : ConfigurationElementCollection + { + /// + /// Gets or sets a value indicating whether to auto load all plugins. + /// Defaults to True. + /// + /// If True plugins are auto discovered and loaded from all assemblies otherwise they must be defined in the configuration file + [ConfigurationProperty("autoLoadPlugins", DefaultValue = true, IsRequired = false)] + public bool AutoLoadPlugins + { + get { return (bool)this["autoLoadPlugins"]; } + + set { this["autoLoadPlugins"] = value; } + } + + /// + /// Gets the type of the . + /// + /// + /// The of this collection. + /// + public override ConfigurationElementCollectionType CollectionType + { + get { return ConfigurationElementCollectionType.BasicMap; } + } + + /// + /// Gets the name used to identify this collection of elements in the configuration file when overridden in a derived class. + /// + /// + /// The name of the collection; otherwise, an empty string. The default is an empty string. + /// + protected override string ElementName + { + get { return "service"; } + } + + /// + /// Gets or sets the + /// at the specified index within the collection. + /// + /// The index at which to get the specified object. + /// + /// The + /// at the specified index within the collection. + /// + public ServiceElement this[int index] + { + get + { + return (ServiceElement)BaseGet(index); + } + + set + { + if (this.BaseGet(index) != null) + { + this.BaseRemoveAt(index); + } + + this.BaseAdd(index, value); + } + } + + /// + /// When overridden in a derived class, creates a new . + /// + /// + /// A new . + /// + protected override ConfigurationElement CreateNewElement() + { + return new ServiceElement(); + } + + /// + /// Gets the element key for a specified configuration element when overridden in a derived class. + /// + /// + /// An that acts as the key for the specified . + /// + /// The to return the key for. + protected override object GetElementKey(ConfigurationElement element) + { + return ((ServiceElement)element).Name; + } + } + + /// + /// Represents a SettingElement configuration element within the configuration. + /// + public class SettingElement : ConfigurationElement + { + /// + /// Gets or sets the key of the plugin setting. + /// + /// The key of the plugin setting. + [ConfigurationProperty("key", IsRequired = true, IsKey = true)] + public string Key + { + get + { + return this["key"] as string; + } + + set + { + this["key"] = value; + } + } + + /// + /// Gets or sets the value of the plugin setting. + /// + /// The value of the plugin setting. + [ConfigurationProperty("value", IsRequired = true)] + public string Value + { + get + { + return (string)this["value"]; + } + + set + { + this["value"] = value; + } + } + } + + /// + /// Represents a SettingElementCollection collection configuration element within the configuration. + /// + public class SettingElementCollection : ConfigurationElementCollection + { + /// + /// Gets the type of the . + /// + /// + /// The of this collection. + /// + public override ConfigurationElementCollectionType CollectionType + { + get { return ConfigurationElementCollectionType.BasicMap; } + } + + /// + /// Gets the name used to identify this collection of elements in the configuration file when overridden in a derived class. + /// + /// + /// The name of the collection; otherwise, an empty string. The default is an empty string. + /// + protected override string ElementName + { + get { return "setting"; } + } + + /// + /// Gets or sets the + /// at the specified index within the collection. + /// + /// The index at which to get the specified object. + /// + /// The the + /// at the specified index within the collection. + /// + public ImageProcessingSection.SettingElement this[int index] + { + get + { + return (ImageProcessingSection.SettingElement)BaseGet(index); + } + + set + { + if (this.BaseGet(index) != null) + { + this.BaseRemoveAt(index); + } + + this.BaseAdd(index, value); + } + } + + /// + /// Returns the setting element with the specified key. + /// + /// the key representing the element + /// the setting element + public new ImageProcessingSection.SettingElement this[string key] + { + get { return (ImageProcessingSection.SettingElement)BaseGet(key); } + } + + /// + /// Returns a value indicating whether the settings collection contains the + /// given object. + /// + /// The key to identify the setting. + /// True if the collection contains the key; otherwise false. + public bool ContainsKey(string key) + { + object[] keys = BaseGetAllKeys(); + + return keys.Any(obj => (string)obj == key); + } + + /// + /// Gets the element key for a specified PluginElement configuration element. + /// + /// + /// The ConfigurationElement + /// to return the key for. + /// + /// The element key for a specified PluginElement configuration element. + protected override object GetElementKey(ConfigurationElement element) + { + return ((ImageProcessingSection.SettingElement)element).Key; + } + + /// + /// Creates a new SettingElement configuration element. + /// + /// + /// A new SettingElement configuration element. + /// + protected override ConfigurationElement CreateNewElement() + { + return new ImageProcessingSection.SettingElement(); + } + } + /// /// Represents a whitelist collection configuration element within the configuration. /// diff --git a/src/ImageProcessor.Web/ImageProcessor.Web.csproj b/src/ImageProcessor.Web/ImageProcessor.Web.csproj index 47b69f8ac..05b2299b1 100644 --- a/src/ImageProcessor.Web/ImageProcessor.Web.csproj +++ b/src/ImageProcessor.Web/ImageProcessor.Web.csproj @@ -46,6 +46,7 @@ + @@ -86,6 +87,8 @@ + + diff --git a/src/ImageProcessor.Web/Services/IImageService.cs b/src/ImageProcessor.Web/Services/IImageService.cs new file mode 100644 index 000000000..07b5d43fc --- /dev/null +++ b/src/ImageProcessor.Web/Services/IImageService.cs @@ -0,0 +1,51 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) James South. +// Licensed under the Apache License, Version 2.0. +// +// +// Defines properties and methods for allowing retrieval of image from different means. +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace ImageProcessor.Web.Services +{ + using System.Collections.Generic; + using System.Threading.Tasks; + + /// + /// Defines properties and methods for allowing retrieval of image from different means. + /// + public interface IImageService + { + /// + /// Gets the key for the given implementation. + /// + /// This value is used as a prefix for any image requests that should use this service. + /// + /// + string Key { get; } + + /// + /// Gets a value indicating whether the image service requests files from + /// the locally based file system. + /// + bool IsFileLocalService { get; } + + /// + /// Gets or sets any additional settings required by the service. + /// + Dictionary Settings { get; set; } + + /// + /// Gets the image using the given identifier. + /// + /// + /// The value identifying the image to fetch. + /// + /// + /// The array containing the image data. + /// + Task GetImage(object id); + } +} diff --git a/src/ImageProcessor.Web/Services/LocalFileImageService.cs b/src/ImageProcessor.Web/Services/LocalFileImageService.cs new file mode 100644 index 000000000..b961f6455 --- /dev/null +++ b/src/ImageProcessor.Web/Services/LocalFileImageService.cs @@ -0,0 +1,86 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) James South. +// Licensed under the Apache License, Version 2.0. +// +// +// The local file image service for retrieving images from the file system. +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace ImageProcessor.Web.Services +{ + using System.Collections.Generic; + using System.IO; + using System.Threading.Tasks; + using System.Web; + + /// + /// The local file image service for retrieving images from the file system. + /// + public class LocalFileImageService : IImageService + { + /// + /// Gets the key for the given implementation. + /// + /// This value is used as a prefix for any image requests that should use this service. + /// + /// + public string Key + { + get + { + return string.Empty; + } + } + + /// + /// Gets a value indicating whether the image service requests files from + /// the locally based file system. + /// + public bool IsFileLocalService + { + get + { + return true; + } + } + + /// + /// Gets or sets any additional settings required by the service. + /// + public Dictionary Settings { get; set; } + + /// + /// Gets the image using the given identifier. + /// + /// + /// The value identifying the image to fetch. + /// + /// + /// The array containing the image data. + /// + public async Task GetImage(object id) + { + string path = id.ToString(); + byte[] buffer; + + // Check to see if the file exists. + // ReSharper disable once AssignNullToNotNullAttribute + FileInfo fileInfo = new FileInfo(path); + + if (!fileInfo.Exists) + { + throw new HttpException(404, "No image exists at " + path); + } + + using (FileStream file = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, true)) + { + buffer = new byte[file.Length]; + await file.ReadAsync(buffer, 0, (int)file.Length); + } + + return buffer; + } + } +} diff --git a/src/ImageProcessor.Web/Services/RemoteImageService.cs b/src/ImageProcessor.Web/Services/RemoteImageService.cs new file mode 100644 index 000000000..55bb14d2d --- /dev/null +++ b/src/ImageProcessor.Web/Services/RemoteImageService.cs @@ -0,0 +1,105 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) James South. +// Licensed under the Apache License, Version 2.0. +// +// +// The remote image service. +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace ImageProcessor.Web.Services +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Net; + using System.Threading.Tasks; + + using ImageProcessor.Web.Helpers; + + /// + /// The remote image service. + /// + public class RemoteImageService : IImageService + { + /// + /// Initializes a new instance of the class. + /// + public RemoteImageService() + { + this.Settings = new Dictionary(); + } + + /// + /// Gets the key for the given implementation. + /// + /// This value is used as a prefix for any image requests that should use this service. + /// + /// + public string Key + { + get + { + return "remote.axd"; + } + } + + /// + /// Gets a value indicating whether the image service requests files from + /// the locally based file system. + /// + public bool IsFileLocalService + { + get + { + return false; + } + } + + /// + /// Gets or sets any additional settings required by the service. + /// + public Dictionary Settings { get; set; } + + /// + /// Gets the image using the given identifier. + /// + /// + /// The value identifying the image to fetch. + /// + /// + /// The array containing the image data. + /// + public async Task GetImage(object id) + { + Uri uri = new Uri(id.ToString()); + RemoteFile remoteFile = new RemoteFile(uri, false); + byte[] buffer = { }; + + // Prevent response blocking. + WebResponse webResponse = await remoteFile.GetWebResponseAsync().ConfigureAwait(false); + + using (MemoryStream memoryStream = new MemoryStream()) + { + using (WebResponse response = webResponse) + { + using (Stream responseStream = response.GetResponseStream()) + { + if (responseStream != null) + { + responseStream.CopyTo(memoryStream); + + // Reset the position of the stream to ensure we're reading the correct part. + memoryStream.Position = 0; + + buffer = memoryStream.ToArray(); + } + } + } + } + + return buffer; + } + } +}