458 lines · 18.5 KB · C#
RawDownload
//******************************************************************************************************//  RazorEngine.cs - Gbtc////  Copyright © 2016, 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://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/05/2016 - J. Ritchie Carroll//       Generated original version of source code.////****************************************************************************************************** using System;using System.Collections.Generic;using System.Diagnostics.CodeAnalysis;using System.IO;using System.Linq;using System.Reflection;using System.Threading.Tasks;using System.Web.Hosting;using GSF.Collections;using GSF.Configuration;using GSF.IO;using RazorEngine;using RazorEngine.Configuration;using RazorEngine.Templating; namespace GSF.Web.Model{    /// <summary>    /// Defines a Razor engine instance for the specified <typeparamref name="TLanguage"/> constraint.    /// </summary>    /// <typeparam name="TLanguage"><see cref="LanguageConstraint"/> for the Razor engine.</typeparam>    public class RazorEngine<TLanguage> : IRazorEngine where TLanguage : LanguageConstraint, new()    {        #region [ Members ]         // Nested Types        private sealed class FileWatchDebugTemplateManager : ITemplateManager, IDisposable        {            #region [ Members ]             // Fields            private readonly ResolvePathTemplateManager m_templateManager;            private readonly InvalidatingCachingProvider m_cachingProvider;            private readonly SafeFileWatcher m_fileWatcher;            private bool m_disposed;             #endregion             #region [ Constructors ]             public FileWatchDebugTemplateManager(string layoutRoot, InvalidatingCachingProvider cachingProvider)            {                m_cachingProvider = cachingProvider;                m_templateManager = new ResolvePathTemplateManager(new[] { layoutRoot });                 m_fileWatcher = new SafeFileWatcher(Path.GetFullPath(layoutRoot), "*.*")                {                    EnableRaisingEvents = true,                    IncludeSubdirectories = true                };                 m_fileWatcher.Changed += m_fileWatcher_Changed;                m_fileWatcher.Created += m_fileWatcher_Changed;                m_fileWatcher.Deleted += m_fileWatcher_Changed;                m_fileWatcher.Renamed += m_fileWatcher_Changed;            }             #endregion             #region [ Methods ]             public void Dispose()            {                if (m_disposed)                    return;                 try                {                    if ((object)m_fileWatcher != null)                    {                        m_fileWatcher.Changed -= m_fileWatcher_Changed;                        m_fileWatcher.Created -= m_fileWatcher_Changed;                        m_fileWatcher.Deleted -= m_fileWatcher_Changed;                        m_fileWatcher.Renamed -= m_fileWatcher_Changed;                        m_fileWatcher.Dispose();                    }                }                finally                {                    m_disposed = true;                }            }             public ITemplateSource Resolve(ITemplateKey key)            {                return m_templateManager.Resolve(key);            }             public ITemplateKey GetKey(string name, ResolveType resolveType, ITemplateKey context)            {                return m_templateManager.GetKey(name, resolveType, context);            }             public void AddDynamic(ITemplateKey key, ITemplateSource source)            {                m_templateManager.AddDynamic(key, source);            }             private void m_fileWatcher_Changed(object sender, FileSystemEventArgs e)            {                m_cachingProvider.InvalidateAll();            }             #endregion        }         // Fields        private readonly IRazorEngineService m_engineService;        private bool m_disposed;         #endregion         #region [ Constructors ]         /// <summary>        /// Creates a new <see cref="RazorEngine{TLanguage}"/> instance.        /// </summary>        /// <param name="templatePath">Template path for view files.</param>        public RazorEngine(string templatePath)        {            if (string.IsNullOrEmpty(templatePath))                throw new ArgumentNullException(nameof(templatePath));             TLanguage languageType = new TLanguage();             switch (languageType.ResolutionMode)            {                case RazorViewResolutionMode.ResolvePath:                case RazorViewResolutionMode.WatchingResolvePath:                    if (!Directory.Exists(templatePath))                        throw new DirectoryNotFoundException();                     if (languageType.ResolutionMode == RazorViewResolutionMode.ResolvePath)                    {                        m_engineService = RazorEngineService.Create(new TemplateServiceConfiguration                        {                            Language = languageType.TargetLanguage,                            TemplateManager = new ResolvePathTemplateManager(new[] { templatePath }),                            Debug = false                        });                    }                    else                    {                        // The watching resolve path template manager should not be used in production since                        // assemblies cannot be unloaded from an AppDomain. Every time a change to a .cshtml                        // file is picked up by the watcher it is compiled and loaded into the AppDomain and                        // the old one cannot be removed (.NET restriction), the net result is a memory leak                        InvalidatingCachingProvider cachingProvider = new InvalidatingCachingProvider();                         m_engineService = RazorEngineService.Create(new TemplateServiceConfiguration                        {                            Language = languageType.TargetLanguage,                            CachingProvider = cachingProvider,                            TemplateManager = new FileWatchDebugTemplateManager(templatePath, cachingProvider),                            Debug = true                        });                    }                    break;                case RazorViewResolutionMode.EmbeddedResource:                    m_engineService = RazorEngineService.Create(new TemplateServiceConfiguration                    {                        Language = languageType.TargetLanguage,                        TemplateManager = new DelegateTemplateManager(name =>                        {                            string resourceName = name;                             if (!resourceName.Contains("."))                                resourceName = templatePath + name;                             Stream stream = WebExtensions.OpenEmbeddedResourceStream(resourceName);                             if ((object)stream == null)                                return "";                             using (StreamReader reader = new StreamReader(stream))                                return reader.ReadToEnd();                        }),                        Debug = false                    });                    break;            }             TemplatePath = templatePath;        }         #endregion         #region [ Properties ]         /// <summary>        /// Gets the template path defined for this <see cref="RazorEngine{TLanguage}"/>.        /// </summary>        public string TemplatePath        {            get;        }         /// <summary>        /// Gets the <see cref="IRazorEngineService"/> instance used by the <see cref="RazorEngine{TLanguage}"/>.        /// </summary>        public IRazorEngineService EngineService => m_engineService;         #endregion         #region [ Methods ]         /// <summary>        /// Releases all the resources used by the <see cref="RazorEngine{TLanguage}"/> object.        /// </summary>        public void Dispose()        {            Dispose(true);            GC.SuppressFinalize(this);        }         /// <summary>        /// Releases the unmanaged resources used by the <see cref="RazorEngine{TLanguage}"/> object and optionally releases the managed resources.        /// </summary>        /// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param>        protected virtual void Dispose(bool disposing)        {            if (!m_disposed)            {                try                {                    if (disposing)                        m_engineService?.Dispose();                }                finally                {                    m_disposed = true;  // Prevent duplicate dispose.                }            }        }         /// <summary>        /// Gets a given key from the <see cref="ITemplateManager" /> implementation. See <see cref="ITemplateManager.GetKey(String,ResolveType,ITemplateKey)" />.        /// </summary>        /// <returns></returns>        public ITemplateKey GetKey(string name, ResolveType resolveType = ResolveType.Global, ITemplateKey context = null)        {            return m_engineService.GetKey(name, resolveType, context);        }         /// <summary>Checks if a given template is already cached.</summary>        /// <returns></returns>        public bool IsTemplateCached(ITemplateKey key, Type modelType)        {            return m_engineService.IsTemplateCached(key, modelType);        }         /// <summary>        /// Adds a given template to the template manager as dynamic template.        /// </summary>        public void AddTemplate(ITemplateKey key, ITemplateSource templateSource)        {            m_engineService.AddTemplate(key, templateSource);        }         /// <summary>        /// Compiles the specified template and caches it.        /// </summary>        public void Compile(ITemplateKey key, Type modelType = null)        {            m_engineService.Compile(key, modelType);        }         /// <summary>        /// Compiles the specified template, by name, and caches it.        /// </summary>        public void Compile(string name, Type modelType = null)        {            m_engineService.Compile(name, modelType);        }         /// <summary>        /// Runs the given cached template. When the cache does not contain the template it will be compiled and cached beforehand.        /// </summary>        public void RunCompile(ITemplateKey key, TextWriter writer, Type modelType = null, object model = null, DynamicViewBag viewBag = null)        {            m_engineService.RunCompile(key, writer, modelType, model, viewBag);        }         /// <summary>        /// Runs the given cached template.        /// </summary>        public void Run(ITemplateKey key, TextWriter writer, Type modelType = null, object model = null, DynamicViewBag viewBag = null)        {            m_engineService.Run(key, writer, modelType, model, viewBag);        }         /// <summary>        /// Kicks off a task to pre-compile Razor templates.        /// </summary>        /// <param name="exceptionHandler">Exception handler used to report issues, if any.</param>        public Task PreCompile(Action<Exception> exceptionHandler)        {            return PreCompile(exceptionHandler, null);        }         /// <summary>        /// Kicks off a task to pre-compile Razor templates.        /// </summary>        /// <param name="exceptionHandler">Exception handler used to report issues, if any.</param>        /// <param name="templatePath">Template path is use; otherwise, set to <c>null</c> to use default path.</param>        public Task PreCompile(Action<Exception> exceptionHandler, string templatePath)        {            return PreCompile(null, exceptionHandler, templatePath);        }         /// <summary>        /// Kicks off a task to pre-compile Razor templates.        /// </summary>        /// <param name="modelType">The type of the model used for the application.</param>        /// <param name="exceptionHandler">Exception handler used to report issues, if any.</param>        /// <param name="templatePath">Template path is use; otherwise, set to <c>null</c> to use default path.</param>        public Task PreCompile(Type modelType, Action<Exception> exceptionHandler, string templatePath)        {            if (string.IsNullOrEmpty(templatePath))                templatePath = TemplatePath;             return Task.Run(() =>            {                TLanguage languageType = new TLanguage();                 if (languageType.ResolutionMode == RazorViewResolutionMode.EmbeddedResource)                {                    foreach (string fileName in Assembly.GetExecutingAssembly().GetManifestResourceNames().Where(fileName => fileName.StartsWith(templatePath)))                    {                        try                        {                            m_engineService.Compile(fileName, modelType);                        }                        catch (Exception ex)                        {                            if ((object)exceptionHandler != null)                                exceptionHandler(new InvalidOperationException($"Failed to pre-compile razor template \"{fileName}\": {ex.Message}", ex));                        }                    }                }                else                {                    string webRootFolder = FilePath.AddPathSuffix(templatePath);                    string[] razorFiles = FilePath.GetFileList($"{webRootFolder}*.{(languageType.TargetLanguage == Language.CSharp ? "cs" : "vb")}html");                     foreach (string fileName in razorFiles)                    {                        try                        {                            m_engineService.Compile(FilePath.GetFileName(fileName), modelType);                        }                        catch (Exception ex)                        {                            if ((object)exceptionHandler != null)                                exceptionHandler(new InvalidOperationException($"Failed to pre-compile razor template \"{fileName}\": {ex.Message}", ex));                        }                    }                }            });        }         #endregion         #region [ Static ]         // Static Fields        private static RazorEngine<TLanguage> s_defaultEngine;        private static readonly Dictionary<string, RazorEngine<TLanguage>> s_configuredEngines;         // Static Properties         /// <summary>        /// Gets default configured razor engine instance.        /// </summary>        public static RazorEngine<TLanguage> Default => s_defaultEngine ?? (s_defaultEngine = GetConfiguredEngine());         // Static Constructor        static RazorEngine()        {            s_configuredEngines = new Dictionary<string, RazorEngine<TLanguage>>(StringComparer.OrdinalIgnoreCase);        }         // Static Methods         /// <summary>        /// Gets a shared <see cref="RazorEngine{TLanguage}"/> instance created based on configured template path defined in the specified <paramref name="settingsCategory"/>.        /// </summary>        /// <param name="settingsCategory">Settings category to use for template path settings; defaults to "systemSettings".</param>        /// <returns>Shared <see cref="RazorEngine{TLanguage}"/> instance created based on configured template path.</returns>        [SuppressMessage("Microsoft.Reliability", "CA2002:DoNotLockOnObjectsWithWeakIdentity")]        public static RazorEngine<TLanguage> GetConfiguredEngine(string settingsCategory = null)        {            lock (typeof(LanguageConstraint))            {                if (string.IsNullOrWhiteSpace(settingsCategory))                    settingsCategory = "systemSettings";                 return s_configuredEngines.GetOrAdd(settingsCategory, category =>                {                    string templatePath = null;                     // Get configured template path                    CategorizedSettingsElementCollection settings = ConfigurationFile.Current.Settings[category];                     TLanguage languageType = new TLanguage();                     switch (languageType.ResolutionMode)                    {                        case RazorViewResolutionMode.ResolvePath:                        case RazorViewResolutionMode.WatchingResolvePath:                            if (Common.GetApplicationType() == ApplicationType.Web)                            {                                settings.Add("TemplatePath", "~/Views/Shared", "Path for data context based razor field templates.");                                templatePath = HostingEnvironment.MapPath(settings["TemplatePath"].Value).EnsureEnd('/');                            }                            else                            {                                settings.Add("TemplatePath", "Eval(systemSettings.WebRootPath)", "Path for data context based razor field templates.");                                templatePath = FilePath.GetAbsolutePath(settings["TemplatePath"].Value);                            }                            break;                        case RazorViewResolutionMode.EmbeddedResource:                            settings.Add("EmbeddedTemplatePath", "GSF.Web.Model.Views.", "Embedded name space path for data context based razor field templates.");                            templatePath = settings["EmbeddedTemplatePath"].Value.EnsureEnd('.');                            break;                    }                     return new RazorEngine<TLanguage>(templatePath);                });            }        }         #endregion    }}

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t