//****************************************************************************************************** // 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("virtualPath"); } if (virtualPath.Length == 0) { throw new ArgumentOutOfRangeException("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("virtualPath"); } if (virtualPath.Length == 0) { throw new ArgumentOutOfRangeException("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("virtualPath"); } if (virtualPath.Length == 0) { throw new ArgumentOutOfRangeException("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("paramName"); } if (path.Length == 0) { throw new ArgumentOutOfRangeException("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("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 } }