//******************************************************************************************************
// EmbeddedResourcePathProvider.cs - Gbtc
//
// Copyright © 2012, Grid Protection Alliance. All Rights Reserved.
//
// Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See
// the NOTICE file distributed with this work for additional information regarding copyright ownership.
// The GPA licenses this file to you under the MIT License (MIT), the "License"; you may
// not use this file except in compliance with the License. You may obtain a copy of the License at:
//
// http://www.opensource.org/licenses/MIT
//
// Unless agreed to in writing, the subject software distributed under the License is distributed on an
// "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the
// License for the specific language governing permissions and limitations.
//
// Code Modification History:
// ----------------------------------------------------------------------------------------------------
// 05/04/2010 - Pinal C. Patel
// Generated original version of source code.
// 12/20/2012 - Starlynn Danyelle Gilliam
// Modified Header.
//
//******************************************************************************************************
using System;
using System.Collections;
using System.Collections.Generic;
using System.Configuration;
using System.IO;
using System.Reflection;
using System.Web;
using System.Web.Caching;
using System.Web.Hosting;
using GSF.Configuration;
using GSF.IO;
namespace GSF.Web.Hosting
{
///
/// A that allows serving
/// pages from embedded resources.
///
///
///
/// ASP.NET retrieves files to serve via the .
/// Rather than opening a file via , you ask
/// the for its
///
/// and ask that provider for the file. The provider will return a
/// reference that will allow you
/// to open a stream on the file and use the contents.
///
///
/// This implementation of
/// allows you to serve files to ASP.NET through embedded resources. Rather
/// than deploying your web forms, user controls, etc., to the file system,
/// you can embed the files as resources right in your assembly and deploy
/// just your assembly. The
/// mechanism will take care of the rest.
///
///
/// Most implementations
/// handle both directories and files. This implementation handles only files.
/// As such, if the is
/// used to enumerate available files (as in directory browsing), files provided
/// via embedded resource will not be included.
///
///
/// To use this , you need
/// to do four things to your web application.
///
///
/// First, you need to set all of the files you want to serve from your assembly
/// as embedded resources. By default, web forms and so forth are set as "content"
/// files; setting them as embedded resources will package them into your assembly.
///
///
/// Second, in your AssemblyInfo.cs file (or whichever file you are
/// declaring your assembly attributes in) you need to add one
/// for
/// every file you plan on serving. This lets the provider know which embedded
/// resources are available and which are actually resources for other purposes.
/// Your assembly attributes will look something like this:
///
///
/// [assembly: EmbeddedResourceFileAttribute("MyNamespace.WebForm1.aspx", "MyNamespace")]
/// [assembly: EmbeddedResourceFileAttribute("MyNamespace.UserControl1.ascx", "MyNamespace")]
///
///
/// Third, you need to register this provider at application startup so ASP.NET
/// knows to use it. In your Global.asax, during Application_OnStart,
/// put the following:
///
///
/// System.Web.Hosting.HostingEnvironment.RegisterVirtualPathProvider(new EmbeddedResourcePathProvider());
///
///
/// Fourth, in your web.config file, you need to set up a configuration
/// section called embeddedFileAssemblies that lets the provider know
/// which assemblies should be queried for embedded files. A sample configuration
/// section looks like this:
///
///
/// <configuration>
/// <configSections>
/// <section name="embeddedFileAssemblies" type="GSF.Configuration.StringCollectionSectionHandler, GSF.Web.Hosting.EmbeddedResourcePathProvider"/>
/// </configSections>
/// <embeddedFileAssemblies>
/// <add value="My.Web.Assembly"/>
/// </embeddedFileAssemblies>
/// <!-- ... other web.config items ... -->
/// </configuration>
///
///
/// Once you have that set up, you're ready to serve files from embedded resources.
/// Simply deploy your application without putting the embedded resource files
/// into the filesystem. When you visit the embedded locations, the provider
/// will automatically retrieve the proper embedded resource.
///
///
/// File paths are mapped into the application using the
///
/// declarations and the
/// method. This allows you to set up your web application as normal in
/// Visual Studio and the folder structure, which automatically generates
/// namespaces for your embedded resources, will translate into virtual folders
/// in the embedded resource "filesystem."
///
///
/// By default, files that are embedded as resources will take precedence over
/// files in the filesystem. If you would like the files in the filesystem
/// to take precedence (that is, if you would like to allow the filesystem
/// to "override" embedded files), you can set a key in the appSettings
/// section of your web.config file that enables overrides:
///
///
/// <configuration>
/// <!-- ... other web.config items ... -->
/// <appSettings>
/// <add key="GSF.Web.Hosting.EmbeddedResourcePathProvider.AllowOverrides" value="true"/>
/// </appSettings>
/// </configuration>
///
///
/// For more information on virtual filesystems in ASP.NET, check out
/// .
///
///
///
///
public class EmbeddedResourcePathProvider : VirtualPathProvider
{
#region EmbeddedResourcePathProvider Variables
#region Constants
///
/// The standard web application "app relative root" path.
///
///
private const string ApplicationRootPath = "~/";
///
/// The name of the configuration section containing the list of assemblies
/// that should participate in the virtual filesystem.
///
///
public const string ConfigSectionName = "embeddedFileAssemblies";
///
/// Appsettings key that indicates if filesystem files should override
/// embedded files.
///
///
public const string ConfigKeyAllowOverrides = "GSF.Web.Hosting.EmbeddedResourcePathProvider.AllowOverrides";
#endregion
#region Instance
///
/// Internal storage for the
///
/// property.
///
///
private readonly VirtualFileBaseCollection _files = new VirtualFileBaseCollection();
#endregion
#endregion
#region EmbeddedResourcePathProvider Properties
///
/// Gets a value indicating if embedded files can be overridden.
///
///
/// if embedded files can be overridden by other
/// files (e.g., if filesystem files override embedded files);
/// if not. Defaults to .
///
///
///
/// This property uses the
/// key
/// to determine if overrides are allowed. If the key is present, it is
/// parsed to a and that value is returned.
/// If the key is not present or if any error occurs during the parsing,
/// is returned.
///
///
///
public virtual bool AllowOverrides
{
get
{
string toParse = ConfigurationManager.AppSettings[ConfigKeyAllowOverrides];
if (string.IsNullOrEmpty(toParse))
{
return false;
}
bool retVal;
bool.TryParse(toParse, out retVal);
return retVal;
}
}
///
/// Gets the collection of files served by this provider.
///
///
/// A
/// that contains all of the files served by this provider.
///
///
public virtual VirtualFileBaseCollection Files
{
get
{
return _files;
}
}
#endregion
#region EmbeddedResourcePathProvider Implementation
#region Constructors
#endregion
#region Overrides
///
/// Gets a value that indicates whether a file exists in the virtual file system.
///
/// The path to the virtual file.
/// if the file exists in the virtual file system; otherwise, .
///
///
/// This override checks to see if the embedded resource file exists
/// in memory. If so, this method will return .
/// If not, it returns the value from the
/// virtual path provider.
///
///
///
/// Thrown if is .
///
///
/// Thrown if is .
///
///
///
public override bool FileExists(string virtualPath)
{
if (virtualPath == null)
{
throw new ArgumentNullException(nameof(virtualPath));
}
if (virtualPath.Length == 0)
{
throw new ArgumentOutOfRangeException(nameof(virtualPath));
}
string absolutePath = VirtualPathUtility.ToAbsolute(virtualPath);
if (this.Files.Contains(absolutePath))
{
return true;
}
return base.FileExists(absolutePath);
}
///
/// Creates a cache dependency based on the specified virtual paths.
///
/// The path to the primary virtual resource.
/// An array of paths to other resources required by the primary virtual resource.
/// The UTC time at which the virtual resources were read.
/// A object for the specified virtual resources.
///
/// Thrown if is .
///
///
/// Thrown if is .
///
///
///
public override CacheDependency GetCacheDependency(string virtualPath, IEnumerable virtualPathDependencies, DateTime utcStart)
{
if (virtualPath == null)
{
throw new ArgumentNullException(nameof(virtualPath));
}
if (virtualPath.Length == 0)
{
throw new ArgumentOutOfRangeException(nameof(virtualPath));
}
string absolutePath = VirtualPathUtility.ToAbsolute(virtualPath);
// Lazy initialize the return value so we can return null if needed
AggregateCacheDependency retVal = null;
// Handle chained dependencies
if (virtualPathDependencies != null)
{
foreach (string virtualPathDependency in virtualPathDependencies)
{
CacheDependency dependencyToAdd = this.GetCacheDependency(virtualPathDependency, null, utcStart);
if (dependencyToAdd == null)
{
// Ignore items that have no dependency
continue;
}
if (retVal == null)
{
retVal = new AggregateCacheDependency();
}
retVal.Add(dependencyToAdd);
}
}
// Handle the primary file
CacheDependency primaryDependency;
if (this.FileHandledByBaseProvider(absolutePath))
{
primaryDependency = base.GetCacheDependency(absolutePath, null, utcStart);
}
else
{
primaryDependency = new CacheDependency(((EmbeddedResourceVirtualFile)this.Files[absolutePath]).ContainingAssembly.Location, utcStart);
}
if (primaryDependency != null)
{
if (retVal == null)
{
retVal = new AggregateCacheDependency();
}
retVal.Add(primaryDependency);
}
return retVal;
}
///
/// Gets a virtual file from the virtual file system.
///
/// The path to the virtual file.
/// A descendent of the class that represents a file in the virtual file system.
///
/// Thrown if is .
///
///
/// Thrown if is .
///
///
///
public override VirtualFile GetFile(string virtualPath)
{
// virtualPath comes in absolute form: /MyApplication/Subfolder/OtherFolder/Control.ascx
// * ToAppRelative: ~/Subfolder/OtherFolder/Control.ascx
// * ToAbsolute: /MyApplication/Subfolder/OtherFolder/Control.ascx
if (virtualPath == null)
{
throw new ArgumentNullException(nameof(virtualPath));
}
if (virtualPath.Length == 0)
{
throw new ArgumentOutOfRangeException(nameof(virtualPath));
}
string absolutePath = VirtualPathUtility.ToAbsolute(virtualPath);
if (this.FileHandledByBaseProvider(absolutePath))
return base.GetFile(absolutePath);
return (VirtualFile)this.Files[absolutePath];
}
///
/// Initializes the instance.
///
///
///
/// The set of assemblies configured to provide embedded resource files
/// are queried for
/// attributes. For each one present, the associated embedded resource
/// is added to the virtual file system served by this provider.
///
///
///
///
protected override void Initialize()
{
string[] assemblies = FilePath.GetFileList(FilePath.GetAbsolutePath(string.Format("bin{0}*.dll", Path.DirectorySeparatorChar)));
foreach (string assembly in assemblies)
{
try
{
this.ProcessEmbeddedFiles(assembly);
}
// Thrown when there's a non-.NET dll in bin. Since that's a valid case, we ignore it.
catch (BadImageFormatException) { }
}
base.Initialize();
}
#endregion
#region [ Methods ]
#region [ Static ]
///
/// Gets the names of the configured assemblies from configuration.
///
///
/// A with
/// the names of the configured assemblies that should participate in this
/// path provider.
///
///
protected static string[] GetConfiguredAssemblyNames()
{
CategorizedSettingsElement setting = ConfigurationFile.Current.Settings["EmbeddedResourceProvider"]["EmbeddedResourceAssemblies"];
if ((object)setting == null)
return new string[] { };
return setting.Value.Split(',');
}
///
/// Maps an embedded resource ID into a web application relative path (~/path).
///
///
/// The base namespace of the resource to map.
///
///
/// The fully qualified embedded resource path to map.
///
/// The mapped path of the resource into the web application.
///
///
/// The is stripped from the front of the
/// and all but the last period in the remaining
/// is replaced with the directory separator character
/// ('/'). Finally, that path is mapped into a web application relative path.
///
///
/// The filename being mapped must have an extension associated with it, and that
/// extension may not have a period in it. Only one period will be kept in the
/// mapped filename - others will be assumed to be directory separators. If a filename
/// has multiple extensions (i.e., My.Custom.config), it will not map properly -
/// it will end up being ~/My/Custom.config.
///
///
/// If does not occur at the start of the
/// , an
/// is thrown.
///
///
///
///
/// Given a of MyNamespace,
/// this method will process as follows:
///
///
///
/// value
/// Mapping in Web App
///
/// -
/// MyNamespace.Config.MyFile.config
/// ~/Config/MyFile.config
///
/// -
/// MyNamespace.MyPage.aspx
/// ~/MyPage.aspx
///
///
///
///
/// Thrown if or is .
///
///
/// Thrown if or :
///
/// -
///
/// Is .
///
///
/// -
///
/// Start or end with period.
///
///
/// -
///
/// Contain two or more periods together (like MyNamespace..MySubnamespace).
///
///
///
///
///
protected static string MapResourceToWebApplication(string baseNamespace, string resourcePath)
{
// Validate parameters
ValidateResourcePath("baseNamespace", baseNamespace);
ValidateResourcePath("resourcePath", resourcePath);
// Ensure that the base namespace (with the period delimiter) appear in the resource path
if (resourcePath.IndexOf(baseNamespace + ".") != 0)
{
throw new InvalidOperationException("Base resource namespace must appear at the start of the embedded resource path.");
}
// Remove the base namespace from the resource path
string newResourcePath = resourcePath.Remove(0, baseNamespace.Length + 1);
// Find the last period - that's the file extension
int extSeparator = newResourcePath.LastIndexOf(".", StringComparison.Ordinal);
// Replace all but the last period with a directory separator
string resourceFilePath = newResourcePath.Substring(0, extSeparator).Replace(".", "/") + newResourcePath.Substring(extSeparator, newResourcePath.Length - extSeparator);
// Map the path into the web app and return
string retVal = VirtualPathUtility.Combine("~/", resourceFilePath);
return retVal;
}
///
/// Validates an embedded resource path or namespace.
///
/// The name of the parameter being validated.
/// The path/namespace to validate.
///
/// Thrown if is .
///
///
/// Thrown if :
///
/// -
///
/// Is .
///
///
/// -
///
/// Start or end with period.
///
///
/// -
///
/// Contain two or more periods together (like MyNamespace..MySubnamespace).
///
///
///
///
///
private static void ValidateResourcePath(string paramName, string path)
{
if (path == null)
{
throw new ArgumentNullException(nameof(paramName));
}
if (path.Length == 0)
{
throw new ArgumentOutOfRangeException(nameof(paramName));
}
if (path.StartsWith(".") || path.EndsWith("."))
{
throw new ArgumentOutOfRangeException(paramName, path, paramName + " may not start or end with a period.");
}
if (path.IndexOf("..") >= 0)
{
throw new ArgumentOutOfRangeException(paramName, path, paramName + " may not contain two or more periods together.");
}
}
#endregion
#region [ Instance ]
///
/// Determines if a file should be handled by the base provider or if
/// it should be handled by this provider.
///
/// The absolute path to the file to check.
///
/// if processing of the file at
/// should be done by the base provider;
/// if this provider should handle it.
///
///
private bool FileHandledByBaseProvider(string absolutePath)
{
return (this.AllowOverrides && base.FileExists(absolutePath)) ||
!this.Files.Contains(absolutePath);
}
///
/// Reads in the embedded files from an assembly an processes them into
/// the virtual file system.
///
/// The name of the to load and process.
///
/// Thrown if is .
///
///
/// Thrown if is .
///
///
/// Thrown if the indicated by
/// is not found.
///
///
///
/// The will be passed to
/// so the associated assembly can be processed. If the assembly is not
/// found, a is thrown.
///
///
/// Once the assembly is retrieved, it is queried for
/// instances. For each one found, the associated resources are processed
/// into virtual files that will be stored in
///
/// for later use.
///
///
///
///
protected virtual void ProcessEmbeddedFiles(string assemblyName)
{
if (string.IsNullOrEmpty(assemblyName))
throw new ArgumentNullException(nameof(assemblyName));
Assembly assembly = Assembly.LoadFrom(FilePath.GetAbsolutePath(assemblyName));
// Get the embedded files specified in the assembly; bail early if there aren't any.
EmbeddedResourceFileAttribute[] attribs = (EmbeddedResourceFileAttribute[])assembly.GetCustomAttributes(typeof(EmbeddedResourceFileAttribute), true);
if (attribs.Length == 0)
{
return;
}
// Get the complete set of embedded resource names in the assembly; bail early if there aren't any.
List assemblyResourceNames = new List(assembly.GetManifestResourceNames());
if (assemblyResourceNames.Count == 0)
{
return;
}
foreach (EmbeddedResourceFileAttribute attrib in attribs)
{
// Ensure the resource specified actually exists in the assembly
if (!assemblyResourceNames.Contains(attrib.ResourcePath))
{
continue;
}
// Map the path into the web application
string mappedPath;
try
{
mappedPath = VirtualPathUtility.ToAbsolute(MapResourceToWebApplication(attrib.ResourceNamespace, attrib.ResourcePath));
}
catch (ArgumentNullException)
{
continue;
}
catch (ArgumentOutOfRangeException)
{
continue;
}
// Create the file and ensure it's unique
EmbeddedResourceVirtualFile file = new EmbeddedResourceVirtualFile(mappedPath, assembly, attrib.ResourcePath);
if (this.Files.Contains(file.VirtualPath))
{
continue;
}
// The file is unique; add it to the filesystem
this.Files.Add(file);
}
}
#endregion
#endregion
#endregion
}
}