History

GSF.Web.Model: Refactored razor view, data context and web page controller implementations to use an external razor engine instance for wider use-case applicability

Ritchie Carroll committed 10 years ago

Commit 5b5cfbf1 parent ead8c0e9 files · +708 −534Browse files

Unified · Split

9 files
  1. Source/Libraries/GSF.Web/GSF.Web.csproj +4 −1
  2. Source/Libraries/GSF.Web/Hosting/WebPageController.cs +77 −95
  3. Source/Libraries/GSF.Web/Model/DataContext.cs +33 −47
  4. Source/Libraries/GSF.Web/Model/IRazorEngine.cs +49 −0
  5. Source/Libraries/GSF.Web/Model/IRazorView.cs +0 −112
  6. Source/Libraries/GSF.Web/Model/LanguageConstraint.cs +112 −0
  7. Source/Libraries/GSF.Web/Model/RazorEngine.cs +330 −0
  8. Source/Libraries/GSF.Web/Model/RazorView.cs +56 −279
  9. Source/Libraries/GSF.Web/Model/RazorViewResolutionMode.cs +47 −0
Source/Libraries/GSF.Web/GSF.Web.csproj+4−1
@@ -221,11 +221,14 @@ · 220 unmodified lines
221221 <Compile Include="Hosting\WebPageController.cs" />
222222 <Compile Include="IgnoreServicesSecurityModule.cs" />
223223 <Compile Include="Model\DataContext.cs" />
224− <Compile Include="Model\IRazorView.cs" />
224+ <Compile Include="Model\IRazorEngine.cs" />
225225 <Compile Include="Model\IRecordOperationsHub.cs" />
226226 <Compile Include="Model\Label.cs" />
227227 <Compile Include="Model\IDLabel.cs" />
228+ <Compile Include="Model\LanguageConstraint.cs" />
228229 <Compile Include="Model\NamespaceDoc.cs" />
230+ <Compile Include="Model\RazorEngine.cs" />
231+ <Compile Include="Model\RazorViewResolutionMode.cs" />
229232 <Compile Include="Model\RecordOperationsCache.cs" />
230233 <Compile Include="NamespaceDoc.cs" />
231234 <Compile Include="Properties\AssemblyInfo.cs" />
Source/Libraries/GSF.Web/Hosting/WebPageController.cs+77−95
@@ -32,6 +32,7 @@ using System.Text; · 31 unmodified lines
3232 using System.Threading.Tasks;
3333 using System.Web;
3434 using System.Web.Http;
35+using GSF.Data;
3536 using GSF.IO;
3637 using GSF.IO.Checksums;
3738 using GSF.Reflection;
@@ -58,15 +59,42 @@ namespace GSF.Web.Hosting · 20 unmodified lines
5859 /// </summary>
5960 public event EventHandler<string> StatusMessage;
6061
62+ // Fields
63+ private readonly string m_webRootPath;
64+ private readonly IRazorEngine m_razorEngineCS;
65+ private readonly IRazorEngine m_razorEngineVB;
66+ private readonly ConcurrentDictionary<string, uint> m_etagCache;
67+ private readonly SafeFileWatcher m_fileWatcher;
68+ private bool m_disposed;
69+
6170 #endregion
6271
6372 #region [ Constructors ]
64−
73+
6574 /// <summary>
6675 /// Creates a new <see cref="WebPageController"/>.
6776 /// </summary>
68− public WebPageController()
77+ /// <param name="webRootPath">Root path for web page; defaults to template path for <paramref name="razorEngineCS"/>.</param>
78+ /// <param name="razorEngineCS">Razor engine instance for .cshtml templates; uses default instance if not provided.</param>
79+ /// <param name="razorEngineVB">Razor engine instance for .vbhtml templates; uses default instance if not provided.</param>
80+ public WebPageController(string webRootPath = null, IRazorEngine razorEngineCS = null, IRazorEngine razorEngineVB = null)
6981 {
82+ bool releaseMode = !AssemblyInfo.EntryAssembly.Debuggable;
83+ m_razorEngineCS = razorEngineCS ?? (releaseMode ? RazorEngine<CSharp>.Default : RazorEngine<CSharpDebug>.Default as IRazorEngine);
84+ m_razorEngineVB = razorEngineVB ?? (releaseMode ? RazorEngine<VisualBasic>.Default : RazorEngine<VisualBasicDebug>.Default as IRazorEngine);
85+ m_webRootPath = FilePath.AddPathSuffix(webRootPath ?? m_razorEngineCS.TemplatePath);
86+ m_etagCache = new ConcurrentDictionary<string, uint>(StringComparer.InvariantCultureIgnoreCase);
87+
88+ m_fileWatcher = new SafeFileWatcher(m_webRootPath)
89+ {
90+ IncludeSubdirectories = true,
91+ EnableRaisingEvents = true
92+ };
93+
94+ m_fileWatcher.Changed += m_fileWatcher_FileChange;
95+ m_fileWatcher.Deleted += m_fileWatcher_FileChange;
96+ m_fileWatcher.Renamed += m_fileWatcher_FileChange;
97+
7098 DefaultWebPage = "index.html";
7199 ClientCacheEnabled = true;
72100 }
@@ -74,7 +102,7 @@ namespace GSF.Web.Hosting · 1 unmodified line
74102 #endregion
75103
76104 #region [ Properties ]
77−
105+
78106 /// <summary>
79107 /// Gets or sets default web page to use for this <see cref="WebPageController"/>.
80108 /// </summary>
@@ -85,7 +113,7 @@ namespace GSF.Web.Hosting · 4 unmodified lines
85113 }
86114
87115 /// <summary>
88− /// Gets or sets the Razor model instance for this <see cref="WebPageController"/>.
116+ /// Gets or sets the <see cref="RazorView"/> model instance for this <see cref="WebPageController"/>, if any.
89117 /// </summary>
90118 public object RazorModel
91119 {
@@ -94,7 +122,7 @@ namespace GSF.Web.Hosting · 2 unmodified lines
94122 }
95123
96124 /// <summary>
97− /// Gets or sets the Razor model <see cref="Type"/> for this <see cref="WebPageController"/>.
125+ /// Gets or sets the <see cref="RazorView"/> model <see cref="Type"/> for this <see cref="WebPageController"/>, if any.
98126 /// </summary>
99127 public Type RazorModelType
100128 {
@@ -102,6 +130,15 @@ namespace GSF.Web.Hosting · 1 unmodified line
102130 set;
103131 }
104132
133+ /// <summary>
134+ /// Gets or sets data connection to provide to <see cref="RazorView"/> instances in this <see cref="WebPageController"/>, if any.
135+ /// </summary>
136+ public AdoDataConnection RazorConnection
137+ {
138+ get;
139+ set;
140+ }
141+
105142 /// <summary>
106143 /// Gets or sets flag that determines if cache control is enabled for browser clients; default to <c>true</c>.
107144 /// </summary>
@@ -115,6 +152,27 @@ namespace GSF.Web.Hosting · 7 unmodified lines
115152
116153 #region [ Methods ]
117154
155+ /// <summary>
156+ /// Releases the unmanaged resources used by the <see cref="WebPageController"/> object and optionally releases the managed resources.
157+ /// </summary>
158+ /// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param>
159+ protected override void Dispose(bool disposing)
160+ {
161+ if (!m_disposed)
162+ {
163+ try
164+ {
165+ if (disposing)
166+ m_fileWatcher?.Dispose();
167+ }
168+ finally
169+ {
170+ m_disposed = true; // Prevent duplicate dispose.
171+ base.Dispose(disposing); // Call base class Dispose().
172+ }
173+ }
174+ }
175+
118176 /// <summary>
119177 /// Default page request handler.
120178 /// </summary>
@@ -157,22 +215,22 @@ namespace GSF.Web.Hosting · 36 unmodified lines
157215 switch (fileExtension)
158216 {
159217 case ".cshtml":
160− content = await s_razorViewCS(pageName, RazorModel, RazorModelType, OnExecutionException).ExecuteAsync(Request, postData);
218+ content = await new RazorView(m_razorEngineCS, pageName, RazorModel, RazorModelType, RazorConnection, OnExecutionException).ExecuteAsync(Request, postData);
161219 response.Content = new StringContent(content, Encoding.UTF8, "text/html");
162220 break;
163221 case ".vbhtml":
164− content = await s_razorViewVB(pageName, RazorModel, RazorModelType, OnExecutionException).ExecuteAsync(Request, postData);
222+ content = await new RazorView(m_razorEngineVB, pageName, RazorModel, RazorModelType, RazorConnection, OnExecutionException).ExecuteAsync(Request, postData);
165223 response.Content = new StringContent(content, Encoding.UTF8, "text/html");
166224 break;
167225 default:
168− string fileName = FilePath.GetAbsolutePath($"{FilePath.AddPathSuffix(s_templatePath)}{pageName.Replace('/', Path.DirectorySeparatorChar)}");
226+ string fileName = FilePath.GetAbsolutePath($"{m_webRootPath}{pageName.Replace('/', Path.DirectorySeparatorChar)}");
169227
170228 if (File.Exists(fileName))
171229 {
172230 FileStream fileData = null;
173231 uint responseHash = 0;
174232
175− if (ClientCacheEnabled && !s_etagCache.TryGetValue(fileName, out responseHash))
233+ if (ClientCacheEnabled && !m_etagCache.TryGetValue(fileName, out responseHash))
176234 {
177235 // Calculate check-sum for file
178236 await Task.Run(() =>
@@ -191,7 +249,7 @@ namespace GSF.Web.Hosting · 12 unmodified lines
191249 }
192250
193251 responseHash = calculatedHash.Value;
194− s_etagCache.TryAdd(fileName, responseHash);
252+ m_etagCache.TryAdd(fileName, responseHash);
195253 fileData.Seek(0, SeekOrigin.Begin);
196254
197255 OnStatusMessage($"Cache [{responseHash}] added for file \"{fileName}\"");
@@ -200,7 +258,7 @@ namespace GSF.Web.Hosting · 2 unmodified lines
200258
201259 if (PublishResponseContent(response, responseHash))
202260 {
203− if ((object)fileData == null)
261+ if (fileData == null)
204262 fileData = File.OpenRead(fileName);
205263
206264 response.Content = await Task.Run(() => new StreamContent(fileData));
@@ -259,6 +317,14 @@ namespace GSF.Web.Hosting · 52 unmodified lines
259317 StatusMessage?.Invoke(this, message);
260318 }
261319
320+ private void m_fileWatcher_FileChange(object sender, FileSystemEventArgs e)
321+ {
322+ uint responseHash;
323+
324+ if (m_etagCache.TryRemove(e.FullPath, out responseHash))
325+ OnStatusMessage($"Cache [{responseHash}] cleared for file \"{e.FullPath}\"");
326+ }
327+
262328 #region [ Sub-folder Handlers ]
263329
264330 /// <summary>
@@ -409,89 +475,5 @@ namespace GSF.Web.Hosting · 144 unmodified lines
409475 #endregion
410476
411477 #endregion
412−
413− #region [ Static ]
414−
415− // Static Events
416−
417− /// <summary>
418− /// Exposes any execution exceptions that occur during static <see cref="WebPageController"/> initialization.
419− /// </summary>
420− public static event EventHandler<Exception> InitializationExceptions;
421−
422− /// <summary>
423− /// Exposes any status messages that get reported from static <see cref="WebPageController"/> initialization.
424− /// </summary>
425− public static event EventHandler<string> InitializationMessages;
426−
427− // Static Fields
428− private static readonly string s_templatePath;
429− private static readonly Func<string, object, Type, Action<Exception>, IRazorView> s_razorViewCS;
430− private static readonly Func<string, object, Type, Action<Exception>, IRazorView> s_razorViewVB;
431− private static readonly ConcurrentDictionary<string, uint> s_etagCache;
432− private static readonly FileSystemWatcher s_fileWatcher;
433−
434− // Static Constructor
435− static WebPageController()
436− {
437− bool isDebugMode = AssemblyInfo.EntryAssembly.Debuggable;
438−
439− if (isDebugMode)
440− {
441− s_templatePath = RazorView<CSharpDebug>.TemplatePath;
442− s_razorViewCS = (templateName, model, modelType, exceptionHandler) => new RazorView<CSharpDebug>(templateName, model, modelType, exceptionHandler);
443− s_razorViewVB = (templateName, model, modelType, exceptionHandler) => new RazorView<VisualBasicDebug>(templateName, model, modelType, exceptionHandler);
444− }
445− else
446− {
447− s_templatePath = RazorView<CSharp>.TemplatePath;
448− s_razorViewCS = (templateName, model, modelType, exceptionHandler) => new RazorView<CSharp>(templateName, model, modelType, exceptionHandler);
449− s_razorViewVB = (templateName, model, modelType, exceptionHandler) => new RazorView<VisualBasic>(templateName, model, modelType, exceptionHandler);
450− }
451−
452− s_etagCache = new ConcurrentDictionary<string, uint>(StringComparer.InvariantCultureIgnoreCase);
453−
454− s_fileWatcher = new FileSystemWatcher(s_templatePath)
455− {
456− IncludeSubdirectories = true,
457− EnableRaisingEvents = true
458− };
459−
460− s_fileWatcher.Changed += s_fileWatcher_FileChange;
461− s_fileWatcher.Deleted += s_fileWatcher_FileChange;
462− s_fileWatcher.Renamed += s_fileWatcher_FileChange;
463−
464−
465− if (isDebugMode)
466− {
467− RazorView<CSharpDebug>.PreCompile(OnInitializationExceptions);
468− RazorView<VisualBasicDebug>.PreCompile(OnInitializationExceptions);
469− }
470− else
471− {
472− RazorView<CSharp>.PreCompile(OnInitializationExceptions);
473− RazorView<VisualBasic>.PreCompile(OnInitializationExceptions);
474− }
475− }
476−
477− private static void s_fileWatcher_FileChange(object sender, FileSystemEventArgs e)
478− {
479− uint responseHash;
480−
481− if (s_etagCache.TryRemove(e.FullPath, out responseHash))
482− OnInitializationMessages($"Cache [{responseHash}] cleared for file \"{e.FullPath}\"");
483− }
484−
485− private static void OnInitializationExceptions(Exception exception)
486− {
487− InitializationExceptions?.Invoke(null, exception);
488− }
489−
490− private static void OnInitializationMessages(string message)
491− {
492− InitializationMessages?.Invoke(null, message);
493− }
494−
495− #endregion
496478 }
497479 }
Source/Libraries/GSF.Web/Model/DataContext.cs+33−47
@@ -41,42 +41,19 @@ using RazorEngine.Templating; · 40 unmodified lines
4141 namespace GSF.Web.Model
4242 {
4343 /// <summary>
44− /// Defines a default data context object based on embedded resources.
44+ /// Defines a data context for Razor views.
4545 /// </summary>
46− public class DataContext : DataContext<CSharpEmbeddedResource>
47− {
48− /// <summary>
49− /// Creates a new <see cref="DataContext"/>.
50− /// </summary>
51− /// <param name="connection"><see cref="AdoDataConnection"/> to use; defaults to a new connection.</param>
52− /// <param name="disposeConnection">Set to <c>true</c> to dispose the provided <paramref name="connection"/>.</param>
53− /// <param name="exceptionHandler">Delegate to handle exceptions.</param>
54− public DataContext(AdoDataConnection connection = null, bool disposeConnection = false, Action<Exception> exceptionHandler = null) :
55− base(connection, disposeConnection, exceptionHandler)
56− {
57− }
58−
59− /// <summary>
60− /// Creates a new <see cref="DataContext"/> using the specified <paramref name="settingsCategory"/>.
61− /// </summary>
62− /// <param name="settingsCategory">Setting category that contains the connection settings.</param>
63− /// <param name="exceptionHandler">Delegate to handle exceptions.</param>
64− public DataContext(string settingsCategory, Action<Exception> exceptionHandler = null) :
65− base(settingsCategory, exceptionHandler)
66− {
67− }
68− }
69−
70− /// <summary>
71− /// Defines a data context for modeled tables.
72− /// </summary>
73− /// <typeparam name="TLanguage"><see cref="LanguageConstraint"/> type to use for Razor views.</typeparam>
74− public class DataContext<TLanguage> : IDisposable where TLanguage : LanguageConstraint, new()
46+ /// <remarks>
47+ /// This class is used by views to render HTML form input templates, paged view model configurations,
48+ /// provide access to modeled table operations and general database access.
49+ /// </remarks>
50+ public class DataContext : IDisposable
7551 {
7652 #region [ Members ]
7753
7854 // Fields
7955 private AdoDataConnection m_connection;
56+ private readonly IRazorEngine m_razorEngine;
8057 private readonly Dictionary<Type, object> m_tableOperations;
8158 private readonly Action<Exception> m_exceptionHandler;
8259 private readonly Dictionary<string, Tuple<string, string>> m_fieldValidationParameters;
@@ -98,14 +75,16 @@ namespace GSF.Web.Model · 15 unmodified lines
9875 #region [ Constructors ]
9976
10077 /// <summary>
101− /// Creates a new <see cref="DataContext{TLanguage}"/>.
78+ /// Creates a new <see cref="DataContext"/>.
10279 /// </summary>
10380 /// <param name="connection"><see cref="AdoDataConnection"/> to use; defaults to a new connection.</param>
10481 /// <param name="disposeConnection">Set to <c>true</c> to dispose the provided <paramref name="connection"/>.</param>
82+ /// <param name="razorEngine">Razor engine instance to use for data context; set to <c>null</c> to use default embedded resources instance.</param>
10583 /// <param name="exceptionHandler">Delegate to handle exceptions.</param>
106− public DataContext(AdoDataConnection connection = null, bool disposeConnection = false, Action<Exception> exceptionHandler = null)
84+ public DataContext(AdoDataConnection connection = null, bool disposeConnection = false, IRazorEngine razorEngine = null, Action<Exception> exceptionHandler = null)
10785 {
10886 m_connection = connection;
87+ m_razorEngine = razorEngine ?? RazorEngine<CSharpEmbeddedResource>.Default;
10988 m_tableOperations = new Dictionary<Type, object>();
11089 m_exceptionHandler = exceptionHandler;
11190 m_fieldValidationParameters = new Dictionary<string, Tuple<string, string>>();
@@ -117,12 +96,14 @@ namespace GSF.Web.Model · 5 unmodified lines
11796 }
11897
11998 /// <summary>
120− /// Creates a new <see cref="DataContext{TLanguage}"/> using the specified <paramref name="settingsCategory"/>.
99+ /// Creates a new <see cref="DataContext"/> using the specified <paramref name="settingsCategory"/>.
121100 /// </summary>
122101 /// <param name="settingsCategory">Setting category that contains the connection settings.</param>
102+ /// <param name="razorEngine">Razor engine instance to use for data context; set to <c>null</c> to use default embedded resources instance.</param>
123103 /// <param name="exceptionHandler">Delegate to handle exceptions.</param>
124− public DataContext(string settingsCategory, Action<Exception> exceptionHandler = null)
104+ public DataContext(string settingsCategory, IRazorEngine razorEngine = null, Action<Exception> exceptionHandler = null)
125105 {
106+ m_razorEngine = razorEngine ?? RazorEngine<CSharpEmbeddedResource>.Default;
126107 m_tableOperations = new Dictionary<Type, object>();
127108 m_exceptionHandler = exceptionHandler;
128109 m_fieldValidationParameters = new Dictionary<string, Tuple<string, string>>();
@@ -137,7 +118,12 @@ namespace GSF.Web.Model · 8 unmodified lines
137118 #region [ Properties ]
138119
139120 /// <summary>
140− /// Gets the <see cref="AdoDataConnection"/> for this <see cref="DataContext{TLanguage}"/>.
121+ /// Gets reference to <see cref="IRazorEngine"/> used by this <see cref="DataContext"/>.
122+ /// </summary>
123+ public IRazorEngine RazorEngine => m_razorEngine;
124+
125+ /// <summary>
126+ /// Gets the <see cref="AdoDataConnection"/> for this <see cref="DataContext"/>, creating a new one if needed.
141127 /// </summary>
142128 public AdoDataConnection Connection => m_connection ?? (m_connection = new AdoDataConnection(m_settingsCategory));
143129
@@ -178,7 +164,7 @@ namespace GSF.Web.Model · 34 unmodified lines
178164 {
179165 get
180166 {
181− return m_addDateFieldTemplate ?? (m_addDateFieldTemplate = $"{RazorView<TLanguage>.TemplatePath}AddDateField.cshtml");
167+ return m_addDateFieldTemplate ?? (m_addDateFieldTemplate = $"{m_razorEngine.TemplatePath}AddDateField.cshtml");
182168 }
183169 set
184170 {
@@ -193,7 +179,7 @@ namespace GSF.Web.Model · 8 unmodified lines
193179 {
194180 get
195181 {
196− return m_addInputFieldTemplate ?? (m_addInputFieldTemplate = $"{RazorView<TLanguage>.TemplatePath}AddInputField.cshtml");
182+ return m_addInputFieldTemplate ?? (m_addInputFieldTemplate = $"{m_razorEngine.TemplatePath}AddInputField.cshtml");
197183 }
198184 set
199185 {
@@ -208,7 +194,7 @@ namespace GSF.Web.Model · 8 unmodified lines
208194 {
209195 get
210196 {
211− return m_addTextAreaFieldTemplate ?? (m_addTextAreaFieldTemplate = $"{RazorView<TLanguage>.TemplatePath}AddTextAreaField.cshtml");
197+ return m_addTextAreaFieldTemplate ?? (m_addTextAreaFieldTemplate = $"{m_razorEngine.TemplatePath}AddTextAreaField.cshtml");
212198 }
213199 set
214200 {
@@ -223,7 +209,7 @@ namespace GSF.Web.Model · 8 unmodified lines
223209 {
224210 get
225211 {
226− return m_addSelectFieldTemplate ?? (m_addSelectFieldTemplate = $"{RazorView<TLanguage>.TemplatePath}AddSelectField.cshtml");
212+ return m_addSelectFieldTemplate ?? (m_addSelectFieldTemplate = $"{m_razorEngine.TemplatePath}AddSelectField.cshtml");
227213 }
228214 set
229215 {
@@ -238,7 +224,7 @@ namespace GSF.Web.Model · 8 unmodified lines
238224 {
239225 get
240226 {
241− return m_addCheckBoxFieldTemplate ?? (m_addCheckBoxFieldTemplate = $"{RazorView<TLanguage>.TemplatePath}AddCheckBoxField.cshtml");
227+ return m_addCheckBoxFieldTemplate ?? (m_addCheckBoxFieldTemplate = $"{m_razorEngine.TemplatePath}AddCheckBoxField.cshtml");
242228 }
243229 set
244230 {
@@ -251,7 +237,7 @@ namespace GSF.Web.Model · 6 unmodified lines
251237 #region [ Methods ]
252238
253239 /// <summary>
254− /// Releases all the resources used by the <see cref="DataContext{TLanguage}"/> object.
240+ /// Releases all the resources used by the <see cref="DataContext"/> object.
255241 /// </summary>
256242 public void Dispose()
257243 {
@@ -260,7 +246,7 @@ namespace GSF.Web.Model · 2 unmodified lines
260246 }
261247
262248 /// <summary>
263− /// Releases the unmanaged resources used by the <see cref="DataContext{TLanguage}"/> object and optionally releases the managed resources.
249+ /// Releases the unmanaged resources used by the <see cref="DataContext"/> object and optionally releases the managed resources.
264250 /// </summary>
265251 /// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param>
266252 protected virtual void Dispose(bool disposing)
@@ -786,7 +772,7 @@ namespace GSF.Web.Model · 519 unmodified lines
786772 /// <returns>Generated HTML for new date based input field based on specified parameters.</returns>
787773 public string AddDateField(string fieldName, bool required, int maxLength = 0, string inputType = null, string fieldLabel = null, string fieldID = null, string groupDataBinding = null, string labelDataBinding = null, string requiredDataBinding = null, string customDataBinding = null, string dependencyFieldName = null, string toolTip = null, bool initialFocus = false)
788774 {
789− RazorView<TLanguage> addDateFieldTemplate = new RazorView<TLanguage>(AddDateFieldTemplate, m_exceptionHandler);
775+ RazorView addDateFieldTemplate = new RazorView(m_razorEngine, AddDateFieldTemplate, m_exceptionHandler);
790776 DynamicViewBag viewBag = addDateFieldTemplate.ViewBag;
791777
792778 if (string.IsNullOrEmpty(fieldID))
@@ -895,7 +881,7 @@ namespace GSF.Web.Model · 102 unmodified lines
895881 /// <returns>Generated HTML for new input field based on specified parameters.</returns>
896882 public string AddInputField(string fieldName, bool required, int maxLength = 0, string inputType = null, string fieldLabel = null, string fieldID = null, string groupDataBinding = null, string labelDataBinding = null, string requiredDataBinding = null, string customDataBinding = null, string dependencyFieldName = null, string toolTip = null, bool initialFocus = false, bool enableHotLinks = true)
897883 {
898− RazorView<TLanguage> addInputFieldTemplate = new RazorView<TLanguage>(AddInputFieldTemplate, m_exceptionHandler);
884+ RazorView addInputFieldTemplate = new RazorView(m_razorEngine, AddInputFieldTemplate, m_exceptionHandler);
899885 DynamicViewBag viewBag = addInputFieldTemplate.ViewBag;
900886
901887 if (string.IsNullOrEmpty(fieldID))
@@ -998,7 +984,7 @@ namespace GSF.Web.Model · 96 unmodified lines
998984 /// <returns>Generated HTML for new text area field based on specified parameters.</returns>
999985 public string AddTextAreaField(string fieldName, bool required, int maxLength = 0, int rows = 2, string fieldLabel = null, string fieldID = null, string groupDataBinding = null, string labelDataBinding = null, string requiredDataBinding = null, string customDataBinding = null, string dependencyFieldName = null, string toolTip = null, bool initialFocus = false, bool enableHotLinks = true)
1000986 {
1001− RazorView<TLanguage> addTextAreaTemplate = new RazorView<TLanguage>(AddTextAreaFieldTemplate, m_exceptionHandler);
987+ RazorView addTextAreaTemplate = new RazorView(m_razorEngine, AddTextAreaFieldTemplate, m_exceptionHandler);
1002988 DynamicViewBag viewBag = addTextAreaTemplate.ViewBag;
1003989
1004990 if (string.IsNullOrEmpty(fieldID))
@@ -1087,7 +1073,7 @@ namespace GSF.Web.Model · 82 unmodified lines
10871073 /// <returns>Generated HTML for new text field based on specified parameters.</returns>
10881074 public string AddSelectField<TOption>(string fieldName, bool required, string optionValueFieldName, string optionLabelFieldName = null, string optionSortFieldName = null, string fieldLabel = null, string fieldID = null, string groupDataBinding = null, string labelDataBinding = null, string customDataBinding = null, string dependencyFieldName = null, string optionDataBinding = null, string toolTip = null, bool initialFocus = false, RecordRestriction restriction = null) where TOption : class, new()
10891075 {
1090− RazorView<TLanguage> addSelectFieldTemplate = new RazorView<TLanguage>(AddSelectFieldTemplate, m_exceptionHandler);
1076+ RazorView addSelectFieldTemplate = new RazorView(m_razorEngine, AddSelectFieldTemplate, m_exceptionHandler);
10911077 DynamicViewBag viewBag = addSelectFieldTemplate.ViewBag;
10921078 TableOperations<TOption> optionTableOperations = Table<TOption>();
10931079 Dictionary<string, string> options = new Dictionary<string, string>();
@@ -1169,7 +1155,7 @@ namespace GSF.Web.Model · 75 unmodified lines
11691155 /// <returns>Generated HTML for new check box field based on specified parameters.</returns>
11701156 public string AddCheckBoxField(string fieldName, string fieldLabel = null, string fieldID = null, string groupDataBinding = null, string labelDataBinding = null, string customDataBinding = null, string dependencyFieldName = null, string toolTip = null, bool initialFocus = false)
11711157 {
1172− RazorView<TLanguage> addCheckBoxFieldTemplate = new RazorView<TLanguage>(AddCheckBoxFieldTemplate, m_exceptionHandler);
1158+ RazorView addCheckBoxFieldTemplate = new RazorView(m_razorEngine, AddCheckBoxFieldTemplate, m_exceptionHandler);
11731159 DynamicViewBag viewBag = addCheckBoxFieldTemplate.ViewBag;
11741160
11751161 if (string.IsNullOrEmpty(fieldID))
Source/Libraries/GSF.Web/Model/IRazorEngine.cs+49−0
@@ -0,0 +1,49 @@
1+//******************************************************************************************************
2+// IRazorEngine.cs - Gbtc
3+//
4+// Copyright © 2016, Grid Protection Alliance. All Rights Reserved.
5+//
6+// Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See
7+// the NOTICE file distributed with this work for additional information regarding copyright ownership.
8+// The GPA licenses this file to you under the MIT License (MIT), the "License"; you may not use this
9+// file except in compliance with the License. You may obtain a copy of the License at:
10+//
11+// http://opensource.org/licenses/MIT
12+//
13+// Unless agreed to in writing, the subject software distributed under the License is distributed on an
14+// "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the
15+// License for the specific language governing permissions and limitations.
16+//
17+// Code Modification History:
18+// ----------------------------------------------------------------------------------------------------
19+// 05/05/2016 - J. Ritchie Carroll
20+// Generated original version of source code.
21+//
22+//******************************************************************************************************
23+
24+using System;
25+using System.Threading.Tasks;
26+using RazorEngine.Templating;
27+
28+namespace GSF.Web.Model
29+{
30+ /// <summary>
31+ /// Defines an interface for <see cref="RazorEngine{TLanguage}"/> implementations.
32+ /// </summary>
33+ public interface IRazorEngine : IRazorEngineService
34+ {
35+ /// <summary>
36+ /// Gets the template path defined for this <see cref="IRazorEngine"/>.
37+ /// </summary>
38+ string TemplatePath
39+ {
40+ get;
41+ }
42+
43+ /// <summary>
44+ /// Kicks off a task to pre-compile Razor templates.
45+ /// </summary>
46+ /// <param name="exceptionHandler">Exception handler used to report issues, if any.</param>
47+ Task PreCompile(Action<Exception> exceptionHandler = null);
48+ }
49+}
Source/Libraries/GSF.Web/Model/IRazorView.cs+0−112
@@ -1,112 +0,0 @@
1−//******************************************************************************************************
2−// IRazorView.cs - Gbtc
3−//
4−// Copyright © 2016, Grid Protection Alliance. All Rights Reserved.
5−//
6−// Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See
7−// the NOTICE file distributed with this work for additional information regarding copyright ownership.
8−// The GPA licenses this file to you under the MIT License (MIT), the "License"; you may not use this
9−// file except in compliance with the License. You may obtain a copy of the License at:
10−//
11−// http://opensource.org/licenses/MIT
12−//
13−// Unless agreed to in writing, the subject software distributed under the License is distributed on an
14−// "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the
15−// License for the specific language governing permissions and limitations.
16−//
17−// Code Modification History:
18−// ----------------------------------------------------------------------------------------------------
19−// 05/05/2016 - J. Ritchie Carroll
20−// Generated original version of source code.
21−//
22−//******************************************************************************************************
23−
24−using System;
25−using System.Collections.Generic;
26−using System.Net.Http;
27−using System.Threading.Tasks;
28−
29−namespace GSF.Web.Model
30−{
31− /// <summary>
32− /// Defines an interface for <see cref="RazorView{TLanguage}"/> implementations.
33− /// </summary>
34− public interface IRazorView
35− {
36− /// <summary>
37− /// Gets or sets name of template file.
38− /// </summary>
39− string TemplateName
40− {
41− get; set;
42− }
43−
44− /// <summary>
45− /// Gets or sets reference to model to use when rendering template.
46− /// </summary>
47− object Model
48− {
49− get; set;
50− }
51−
52− /// <summary>
53− /// Gets or sets type of <see cref="Model"/>.
54− /// </summary>
55− Type ModelType
56− {
57− get; set;
58− }
59−
60− /// <summary>
61− /// Gets or sets delegate used to handle exceptions.
62− /// </summary>
63− Action<Exception> ExceptionHandler
64− {
65− get; set;
66− }
67−
68− /// <summary>
69− /// Gets reference to view bag used when rendering template.
70− /// </summary>
71− dynamic ViewBag
72− {
73− get;
74− }
75−
76− /// <summary>
77− /// Gets query string parameter specified by <paramref name="key"/>.
78− /// </summary>
79− /// <param name="key">Name of query string parameter to retrieve.</param>
80− /// <returns>Query string parameter specified by <paramref name="key"/>.</returns>
81− string this[string key]
82− {
83− get;
84− }
85−
86− /// <summary>
87− /// Gets a dictionary of query string parameters passed to rendered view.
88− /// </summary>
89− Dictionary<string, string> Parameters
90− {
91− get;
92− }
93−
94− /// <summary>
95− /// Compiles and executes view template.
96− /// </summary>
97− /// <returns>Rendered result.</returns>
98− string Execute();
99−
100− /// <summary>
101− /// Compiles and executes view template for specified request message and post data.
102− /// </summary>
103− /// <returns>Rendered result.</returns>
104− string Execute(HttpRequestMessage requestMessage, dynamic postData);
105−
106− /// <summary>
107− /// Asynchronously compiles and executes view template for specified request message and post data.
108− /// </summary>
109− /// <returns>Task that will provide rendered result.</returns>
110− Task ExecuteAsync(HttpRequestMessage requestMessage, dynamic postData);
111− }
112−}
Source/Libraries/GSF.Web/Model/LanguageConstraint.cs+112−0
@@ -0,0 +1,112 @@
1+//******************************************************************************************************
2+// LanguageConstraint.cs - Gbtc
3+//
4+// Copyright � 2016, Grid Protection Alliance. All Rights Reserved.
5+//
6+// Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See
7+// the NOTICE file distributed with this work for additional information regarding copyright ownership.
8+// The GPA licenses this file to you under the MIT License (MIT), the "License"; you may not use this
9+// file except in compliance with the License. You may obtain a copy of the License at:
10+//
11+// http://opensource.org/licenses/MIT
12+//
13+// Unless agreed to in writing, the subject software distributed under the License is distributed on an
14+// "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the
15+// License for the specific language governing permissions and limitations.
16+//
17+// Code Modification History:
18+// ----------------------------------------------------------------------------------------------------
19+// 05/05/2016 - J. Ritchie Carroll
20+// Generated original version of source code.
21+//
22+//******************************************************************************************************
23+
24+using RazorEngine;
25+
26+namespace GSF.Web.Model
27+{
28+ /// <summary>
29+ /// Defines a language constraint for a <see cref="RazorEngine{TLanguage}"/>.
30+ /// </summary>
31+ public abstract class LanguageConstraint
32+ {
33+ /// <summary>
34+ /// Gets target language for the <see cref="LanguageConstraint"/> implementation.
35+ /// </summary>
36+ public abstract Language TargetLanguage
37+ {
38+ get;
39+ }
40+
41+ /// <summary>
42+ /// Gets resolution mode for <see cref="RazorEngine{TLanguage}"/> views.
43+ /// </summary>
44+ public virtual RazorViewResolutionMode ResolutionMode => RazorViewResolutionMode.ResolvePath;
45+ }
46+
47+ /// <summary>
48+ /// Defines a C# based language constraint.
49+ /// </summary>
50+ public class CSharp : LanguageConstraint
51+ {
52+ /// <summary>
53+ /// Gets target language for the <see cref="LanguageConstraint"/> implementation - C#.
54+ /// </summary>
55+ public override Language TargetLanguage => Language.CSharp;
56+ }
57+
58+ /// <summary>
59+ /// Defines a C# based language constraint in debug mode.
60+ /// </summary>
61+ public class CSharpDebug : CSharp
62+ {
63+ /// <summary>
64+ /// Gets resolution mode for <see cref="RazorEngine{TLanguage}"/> views - watching resolve path for debugging operations.
65+ /// </summary>
66+ public override RazorViewResolutionMode ResolutionMode => RazorViewResolutionMode.WatchingResolvePath;
67+ }
68+
69+ /// <summary>
70+ /// Defines a C# based language constraint for embedded resources.
71+ /// </summary>
72+ public class CSharpEmbeddedResource : CSharp
73+ {
74+ /// <summary>
75+ /// Gets resolution mode for <see cref="RazorEngine{TLanguage}"/> views - embedded resources.
76+ /// </summary>
77+ public override RazorViewResolutionMode ResolutionMode => RazorViewResolutionMode.EmbeddedResource;
78+ }
79+
80+ /// <summary>
81+ /// Defines a Visual Basic based language constraint.
82+ /// </summary>
83+ public class VisualBasic : LanguageConstraint
84+ {
85+ /// <summary>
86+ /// Gets target language for the <see cref="LanguageConstraint"/> implementation - Visual Basic.
87+ /// </summary>
88+ public override Language TargetLanguage => Language.VisualBasic;
89+ }
90+
91+ /// <summary>
92+ /// Defines a Visual Basic based language constraint in debug mode.
93+ /// </summary>
94+ public class VisualBasicDebug : VisualBasic
95+ {
96+ /// <summary>
97+ /// Gets resolution mode for <see cref="RazorEngine{TLanguage}"/> views - watching resolve path for debugging operations.
98+ /// </summary>
99+ public override RazorViewResolutionMode ResolutionMode => RazorViewResolutionMode.WatchingResolvePath;
100+ }
101+
102+ /// <summary>
103+ /// Defines a Visual Basic based language constraint for embedded resources.
104+ /// </summary>
105+ public class VisualBasicEmbeddedResource : VisualBasic
106+ {
107+ /// <summary>
108+ /// Gets resolution mode for <see cref="RazorEngine{TLanguage}"/> views - embedded resources.
109+ /// </summary>
110+ public override RazorViewResolutionMode ResolutionMode => RazorViewResolutionMode.EmbeddedResource;
111+ }
112+}
Source/Libraries/GSF.Web/Model/RazorEngine.cs+330−0
@@ -0,0 +1,330 @@
1+//******************************************************************************************************
2+// RazorEngine.cs - Gbtc
3+//
4+// Copyright © 2016, Grid Protection Alliance. All Rights Reserved.
5+//
6+// Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See
7+// the NOTICE file distributed with this work for additional information regarding copyright ownership.
8+// The GPA licenses this file to you under the MIT License (MIT), the "License"; you may not use this
9+// file except in compliance with the License. You may obtain a copy of the License at:
10+//
11+// http://opensource.org/licenses/MIT
12+//
13+// Unless agreed to in writing, the subject software distributed under the License is distributed on an
14+// "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the
15+// License for the specific language governing permissions and limitations.
16+//
17+// Code Modification History:
18+// ----------------------------------------------------------------------------------------------------
19+// 05/05/2016 - J. Ritchie Carroll
20+// Generated original version of source code.
21+//
22+//******************************************************************************************************
23+
24+using System;
25+using System.Collections.Concurrent;
26+using System.IO;
27+using System.Linq;
28+using System.Reflection;
29+using System.Threading.Tasks;
30+using System.Web.Hosting;
31+using GSF.Configuration;
32+using GSF.IO;
33+using RazorEngine;
34+using RazorEngine.Configuration;
35+using RazorEngine.Templating;
36+
37+namespace GSF.Web.Model
38+{
39+ /// <summary>
40+ /// Defines a Razor engine instance for the specified <typeparamref name="TLanguage"/> constraint.
41+ /// </summary>
42+ /// <typeparam name="TLanguage"><see cref="LanguageConstraint"/> for the Razor engine.</typeparam>
43+ public class RazorEngine<TLanguage> : IRazorEngine where TLanguage : LanguageConstraint, new()
44+ {
45+ #region [ Members ]
46+
47+ // Fields
48+ private readonly IRazorEngineService m_engineService;
49+ private bool m_disposed;
50+
51+ #endregion
52+
53+ #region [ Constructors ]
54+
55+ /// <summary>
56+ /// Creates a new <see cref="RazorEngine{TLanguage}"/> instance.
57+ /// </summary>
58+ /// <param name="templatePath">Template path for view files, if applicable.</param>
59+ public RazorEngine(string templatePath)
60+ {
61+ if (string.IsNullOrEmpty(templatePath))
62+ throw new ArgumentNullException(nameof(templatePath));
63+
64+ TLanguage languageType = new TLanguage();
65+
66+ switch (languageType.ResolutionMode)
67+ {
68+ case RazorViewResolutionMode.ResolvePath:
69+ case RazorViewResolutionMode.WatchingResolvePath:
70+ if (!Directory.Exists(templatePath))
71+ throw new DirectoryNotFoundException();
72+
73+ if (languageType.ResolutionMode == RazorViewResolutionMode.ResolvePath)
74+ {
75+ m_engineService = RazorEngineService.Create(new TemplateServiceConfiguration
76+ {
77+ Language = languageType.TargetLanguage,
78+ TemplateManager = new ResolvePathTemplateManager(new[] { templatePath }),
79+ Debug = false
80+ });
81+ }
82+ else
83+ {
84+ // The watching resolve path template manager should not be used in production since
85+ // assemblies cannot be unloaded from an AppDomain. Every time a change to a .cshtml
86+ // file is picked up by the watcher it is compiled and loaded into the AppDomain and
87+ // the old one cannot be removed (.NET restriction), the net result is a memory leak
88+ InvalidatingCachingProvider cachingProvider = new InvalidatingCachingProvider();
89+
90+ m_engineService = RazorEngineService.Create(new TemplateServiceConfiguration
91+ {
92+ Language = languageType.TargetLanguage,
93+ CachingProvider = cachingProvider,
94+ TemplateManager = new WatchingResolvePathTemplateManager(new[] { templatePath }, cachingProvider),
95+ Debug = true
96+ });
97+ }
98+ break;
99+ case RazorViewResolutionMode.EmbeddedResource:
100+ m_engineService = RazorEngineService.Create(new TemplateServiceConfiguration
101+ {
102+ Language = languageType.TargetLanguage,
103+ TemplateManager = new DelegateTemplateManager(name =>
104+ {
105+ string resourceName = name;
106+
107+ if (!resourceName.Contains("."))
108+ resourceName = templatePath + name;
109+
110+ Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName);
111+
112+ if ((object)stream == null)
113+ return "";
114+
115+ using (StreamReader reader = new StreamReader(stream))
116+ return reader.ReadToEnd();
117+ }),
118+ Debug = false
119+ });
120+ break;
121+ }
122+
123+ TemplatePath = templatePath;
124+ }
125+
126+ #endregion
127+
128+ #region [ Properties ]
129+
130+ /// <summary>
131+ /// Gets the template path defined for this <see cref="RazorEngine{TLanguage}"/>.
132+ /// </summary>
133+ public string TemplatePath
134+ {
135+ get;
136+ }
137+
138+ #endregion
139+
140+ #region [ Methods ]
141+
142+ /// <summary>
143+ /// Releases all the resources used by the <see cref="RazorEngine{TLanguage}"/> object.
144+ /// </summary>
145+ public void Dispose()
146+ {
147+ Dispose(true);
148+ GC.SuppressFinalize(this);
149+ }
150+
151+ /// <summary>
152+ /// Releases the unmanaged resources used by the <see cref="RazorEngine{TLanguage}"/> object and optionally releases the managed resources.
153+ /// </summary>
154+ /// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param>
155+ protected virtual void Dispose(bool disposing)
156+ {
157+ if (!m_disposed)
158+ {
159+ try
160+ {
161+ if (disposing)
162+ m_engineService?.Dispose();
163+ }
164+ finally
165+ {
166+ m_disposed = true; // Prevent duplicate dispose.
167+ }
168+ }
169+ }
170+
171+ /// <summary>
172+ /// Gets a given key from the <see cref="ITemplateManager" /> implementation. See <see cref="ITemplateManager.GetKey(String,ResolveType,ITemplateKey)" />.
173+ /// </summary>
174+ /// <returns></returns>
175+ public ITemplateKey GetKey(string name, ResolveType resolveType = ResolveType.Global, ITemplateKey context = null)
176+ {
177+ return m_engineService.GetKey(name, resolveType, context);
178+ }
179+
180+ /// <summary>Checks if a given template is already cached.</summary>
181+ /// <returns></returns>
182+ public bool IsTemplateCached(ITemplateKey key, Type modelType)
183+ {
184+ return m_engineService.IsTemplateCached(key, modelType);
185+ }
186+
187+ /// <summary>
188+ /// Adds a given template to the template manager as dynamic template.
189+ /// </summary>
190+ public void AddTemplate(ITemplateKey key, ITemplateSource templateSource)
191+ {
192+ m_engineService.AddTemplate(key, templateSource);
193+ }
194+
195+ /// <summary>Compiles the specified template and caches it.</summary>
196+ public void Compile(ITemplateKey key, Type modelType = null)
197+ {
198+ m_engineService.Compile(key, modelType);
199+ }
200+
201+ /// <summary>
202+ /// Runs the given cached template. When the cache does not contain the template it will be compiled and cached beforehand.
203+ /// </summary>
204+ public void RunCompile(ITemplateKey key, TextWriter writer, Type modelType = null, object model = null, DynamicViewBag viewBag = null)
205+ {
206+ m_engineService.RunCompile(key, writer, modelType, model, viewBag);
207+ }
208+
209+ /// <summary>Runs the given cached template.</summary>
210+ public void Run(ITemplateKey key, TextWriter writer, Type modelType = null, object model = null, DynamicViewBag viewBag = null)
211+ {
212+ m_engineService.Run(key, writer, modelType, model, viewBag);
213+ }
214+
215+ /// <summary>
216+ /// Kicks off a task to pre-compile Razor templates.
217+ /// </summary>
218+ /// <param name="exceptionHandler">Exception handler used to report issues, if any.</param>
219+ public Task PreCompile(Action<Exception> exceptionHandler = null)
220+ {
221+ return Task.Run(() =>
222+ {
223+ TLanguage languageType = new TLanguage();
224+
225+ if (languageType.ResolutionMode == RazorViewResolutionMode.EmbeddedResource)
226+ {
227+ foreach (string fileName in Assembly.GetExecutingAssembly().GetManifestResourceNames().Where(fileName => fileName.StartsWith(TemplatePath)))
228+ {
229+ try
230+ {
231+ m_engineService.Compile(fileName.Substring(TemplatePath.Length));
232+ }
233+ catch (Exception ex)
234+ {
235+ if ((object)exceptionHandler != null)
236+ exceptionHandler(new InvalidOperationException($"Failed to pre-compile razor template \"{fileName}\": {ex.Message}", ex));
237+ }
238+ }
239+ }
240+ else
241+ {
242+ string webRootFolder = FilePath.AddPathSuffix(TemplatePath);
243+ string[] razorFiles = FilePath.GetFileList($"{webRootFolder}*.{(languageType.TargetLanguage == Language.CSharp ? "cs" : "vb")}html");
244+
245+ foreach (string fileName in razorFiles)
246+ {
247+ try
248+ {
249+ m_engineService.Compile(FilePath.GetFileName(fileName));
250+ }
251+ catch (Exception ex)
252+ {
253+ if ((object)exceptionHandler != null)
254+ exceptionHandler(new InvalidOperationException($"Failed to pre-compile razor template \"{fileName}\": {ex.Message}", ex));
255+ }
256+ }
257+ }
258+ });
259+ }
260+
261+ #endregion
262+
263+ #region [ Static ]
264+
265+ // Static Fields
266+ private static RazorEngine<TLanguage> s_defaultEngine;
267+ private static readonly ConcurrentDictionary<string, RazorEngine<TLanguage>> s_configuredEngine;
268+
269+ // Static Properties
270+
271+ /// <summary>
272+ /// Gets default configured razor engine instance.
273+ /// </summary>
274+ public static RazorEngine<TLanguage> Default => s_defaultEngine ?? (s_defaultEngine = GetConfiguredEngine());
275+
276+ // Static Constructor
277+ static RazorEngine()
278+ {
279+ s_configuredEngine = new ConcurrentDictionary<string, RazorEngine<TLanguage>>(StringComparer.OrdinalIgnoreCase);
280+ }
281+
282+ // Static Methods
283+
284+ /// <summary>
285+ /// Gets a shared <see cref="RazorEngine{TLanguage}"/> instance created based on configured template path defined in the specified <paramref name="settingsCategory"/>.
286+ /// </summary>
287+ /// <param name="settingsCategory">Settings category to use for template path settings; defaults to "systemSettings".</param>
288+ /// <returns>Shared <see cref="RazorEngine{TLanguage}"/> instance created based on configured template path.</returns>
289+ public static RazorEngine<TLanguage> GetConfiguredEngine(string settingsCategory = null)
290+ {
291+ if (string.IsNullOrWhiteSpace(settingsCategory))
292+ settingsCategory = "systemSettings";
293+
294+ return s_configuredEngine.GetOrAdd(settingsCategory, category =>
295+ {
296+ string templatePath = null;
297+
298+ // Get configured template path
299+ CategorizedSettingsElementCollection systemSettings = ConfigurationFile.Current.Settings[category];
300+
301+ TLanguage languageType = new TLanguage();
302+
303+ switch (languageType.ResolutionMode)
304+ {
305+ case RazorViewResolutionMode.ResolvePath:
306+ case RazorViewResolutionMode.WatchingResolvePath:
307+ if (Common.GetApplicationType() == ApplicationType.Web)
308+ {
309+ systemSettings.Add("TemplatePath", "~/Views/Shared/Templates/", "Path for data context based razor field templates.");
310+ templatePath = HostingEnvironment.MapPath(systemSettings["TemplatePath"].Value).EnsureEnd('/');
311+ }
312+ else
313+ {
314+ systemSettings.Add("TemplatePath", "wwwroot/Templates/", "Path for data context based razor field templates.");
315+ templatePath = FilePath.GetAbsolutePath(systemSettings["TemplatePath"].Value);
316+ }
317+ break;
318+ case RazorViewResolutionMode.EmbeddedResource:
319+ systemSettings.Add("EmbeddedTemplatePath", "GSF.Web.Model.Views.", "Embedded name space path for data context based razor field templates.");
320+ templatePath = systemSettings["EmbeddedTemplatePath"].Value.EnsureEnd('.');
321+ break;
322+ }
323+
324+ return new RazorEngine<TLanguage>(templatePath);
325+ });
326+ }
327+
328+ #endregion
329+ }
330+}
Source/Libraries/GSF.Web/Model/RazorView.cs+56−279
@@ -23,175 +23,59 @@ · 22 unmodified lines
2323
2424 using System;
2525 using System.Collections.Generic;
26−using System.IO;
27−using System.Linq;
2826 using System.Net.Http;
29−using System.Reflection;
3027 using System.Threading.Tasks;
3128 using System.Web;
32−using System.Web.Hosting;
3329 using GSF.Collections;
34−using GSF.Configuration;
35−using GSF.IO;
36−using RazorEngine;
37−using RazorEngine.Configuration;
30+using GSF.Data;
3831 using RazorEngine.Templating;
3932
4033 // ReSharper disable StaticMemberInGenericType
4134 namespace GSF.Web.Model
4235 {
43− /// <summary>
44− /// Defines resolutions modes for Razor view template files.
45− /// </summary>
46− public enum RazorViewResolutionMode
47− {
48− /// <summary>
49− /// Resolves template files from a path.
50− /// </summary>
51− ResolvePath,
52− /// <summary>
53− /// Resolves template files from a path and watches for changes.
54− /// </summary>
55− /// <remarks>
56− /// This should only be used in debug mode.
57− /// </remarks>
58− WatchingResolvePath,
59− /// <summary>
60− /// Resolves template files from an embedded resource.
61− /// </summary>
62− EmbeddedResource
63− }
64−
65− /// <summary>
66− /// Defines a language constraint for a <see cref="RazorView{TLanguage}"/>.
67− /// </summary>
68− public abstract class LanguageConstraint
69− {
70− /// <summary>
71− /// Gets target language for the <see cref="LanguageConstraint"/> implementation.
72− /// </summary>
73− public abstract Language TargetLanguage
74− {
75− get;
76− }
77−
78− /// <summary>
79− /// Gets resolution mode of <see cref="RazorView{TLanguage}"/>.
80− /// </summary>
81− public virtual RazorViewResolutionMode ResolutionMode => RazorViewResolutionMode.ResolvePath;
82− }
83−
84− /// <summary>
85− /// Defines a C# based language constraint.
86− /// </summary>
87− public class CSharp : LanguageConstraint
88− {
89− /// <summary>
90− /// Gets target language for the <see cref="LanguageConstraint"/> implementation - C#.
91− /// </summary>
92− public override Language TargetLanguage => Language.CSharp;
93− }
94−
95− /// <summary>
96− /// Defines a C# based language constraint in debug mode.
97− /// </summary>
98− public class CSharpDebug : CSharp
99− {
100− /// <summary>
101− /// Gets resolution mode of <see cref="RazorView{TLanguage}"/> - watching resolve path for debugging operations.
102− /// </summary>
103− public override RazorViewResolutionMode ResolutionMode => RazorViewResolutionMode.WatchingResolvePath;
104− }
105−
106− /// <summary>
107− /// Defines a C# based language constraint for embedded resources.
108− /// </summary>
109− public class CSharpEmbeddedResource : CSharp
110− {
111− /// <summary>
112− /// Gets resolution mode of <see cref="RazorView{TLanguage}"/> - embedded resources.
113− /// </summary>
114− public override RazorViewResolutionMode ResolutionMode => RazorViewResolutionMode.EmbeddedResource;
115− }
116−
117− /// <summary>
118− /// Defines a Visual Basic based language constraint.
119− /// </summary>
120− public class VisualBasic : LanguageConstraint
121− {
122− /// <summary>
123− /// Gets target language for the <see cref="LanguageConstraint"/> implementation - Visual Basic.
124− /// </summary>
125− public override Language TargetLanguage => Language.VisualBasic;
126− }
127−
128− /// <summary>
129− /// Defines a Visual Basic based language constraint in debug mode.
130− /// </summary>
131− public class VisualBasicDebug : VisualBasic
132− {
133− /// <summary>
134− /// Gets resolution mode of <see cref="RazorView{TLanguage}"/> - watching resolve path for debugging operations.
135− /// </summary>
136− public override RazorViewResolutionMode ResolutionMode => RazorViewResolutionMode.WatchingResolvePath;
137− }
138−
139− /// <summary>
140− /// Defines a Visual Basic based language constraint for embedded resources.
141− /// </summary>
142− public class VisualBasicEmbeddedResource : VisualBasic
143− {
144− /// <summary>
145− /// Gets resolution mode of <see cref="RazorView{TLanguage}"/> - embedded resources.
146− /// </summary>
147− public override RazorViewResolutionMode ResolutionMode => RazorViewResolutionMode.EmbeddedResource;
148− }
149−
15036 /// <summary>
15137 /// Defines a view class for data context based Razor template implementations.
15238 /// </summary>
153− /// <typeparam name="TLanguage"><see cref="LanguageConstraint"/> type to use for Razor views.</typeparam>
154− public class RazorView<TLanguage> : IRazorView where TLanguage : LanguageConstraint, new()
39+ public class RazorView
15540 {
15641 #region [ Members ]
15742
15843 // Fields
44+ private readonly IRazorEngine m_razorEngine;
45+
15946 private readonly DynamicViewBag m_viewBag = new DynamicViewBag();
16047 private Dictionary<string, string> m_parameters;
16148
16249 #endregion
16350
16451 #region [ Constructors ]
165−
166− /// <summary>
167− /// Creates a new <see cref="RazorView{TLanguage}"/>.
168− /// </summary>
169− public RazorView()
170− {
171− }
172−
17352 /// <summary>
174− /// Creates a new <see cref="RazorView{TLanguage}"/> with the specified exception handler.
53+ /// Creates a new <see cref="RazorView"/>.
17554 /// </summary>
55+ /// <param name="razorEngine"><see cref="IRazorEngine"/> instance to use.</param>
17656 /// <param name="templateName">Name of template file, typically a .cshtml or .vbhtml file.</param>
17757 /// <param name="exceptionHandler">Delegate to handle exceptions, if any.</param>
178− public RazorView(string templateName, Action<Exception> exceptionHandler = null)
58+ public RazorView(IRazorEngine razorEngine, string templateName, Action<Exception> exceptionHandler = null) : this(razorEngine, templateName, null, null, null, exceptionHandler)
17959 {
180− TemplateName = templateName;
181− ExceptionHandler = exceptionHandler;
18260 }
18361
18462 /// <summary>
185− /// Creates a new <see cref="RazorView{TLanguage}"/> with the specified parameters.
63+ /// Creates a new <see cref="RazorView"/>.
18664 /// </summary>
65+ /// <param name="razorEngine"><see cref="IRazorEngine"/> instance to use.</param>
18766 /// <param name="templateName">Name of template file, typically a .cshtml or .vbhtml file.</param>
18867 /// <param name="model">Reference to model to use when rendering template.</param>
18968 /// <param name="modelType">Type of <paramref name="model"/>.</param>
69+ /// <param name="connection"><see cref="AdoDataConnection"/> to use, if any.</param>
19070 /// <param name="exceptionHandler">Delegate to handle exceptions, if any.</param>
191− public RazorView(string templateName, object model, Type modelType, Action<Exception> exceptionHandler = null) : this(templateName, exceptionHandler)
71+ public RazorView(IRazorEngine razorEngine, string templateName, object model = null, Type modelType = null, AdoDataConnection connection = null, Action<Exception> exceptionHandler = null)
19272 {
73+ m_razorEngine = razorEngine;
74+ TemplateName = templateName;
19375 Model = model;
19476 ModelType = modelType;
77+ Connection = connection;
78+ ExceptionHandler = exceptionHandler;
19579 }
19680
19781 #endregion
@@ -222,14 +106,6 @@ namespace GSF.Web.Model · 24 unmodified lines
222106 get; set;
223107 }
224108
225− /// <summary>
226− /// Gets or sets delegate used to handle exceptions.
227− /// </summary>
228− public Action<Exception> ExceptionHandler
229− {
230− get; set;
231− }
232−
233109 /// <summary>
234110 /// Gets reference to view bag used when rendering template.
235111 /// </summary>
@@ -259,6 +135,43 @@ namespace GSF.Web.Model · 23 unmodified lines
259135 }
260136 }
261137
138+ /// <summary>
139+ /// Gets reference to <see cref="IRazorEngine"/> used by this <see cref="RazorView"/>.
140+ /// </summary>
141+ public IRazorEngine RazorEngine => m_razorEngine;
142+
143+ /// <summary>
144+ /// Gets or sets data connection to provide to <see cref="DataContext"/>, if any.
145+ /// </summary>
146+ public AdoDataConnection Connection
147+ {
148+ get;
149+ set;
150+ }
151+
152+ /// <summary>
153+ /// Gets or sets delegate used to handle exceptions.
154+ /// </summary>
155+ public Action<Exception> ExceptionHandler
156+ {
157+ get; set;
158+ }
159+
160+ /// <summary>
161+ /// Gets or sets <see cref="IRazorEngine"/> to use for the <see cref="DataContext"/> provided to the view; defaults to <c>null</c>.
162+ /// </summary>
163+ /// <remarks>
164+ /// This should typically not be changed. When value is <c>null</c>, the data context will use the default
165+ /// <see cref="RazorEngine{TLanguage}"/> instance for <see cref="CSharpEmbeddedResource"/> which is used
166+ /// to generate template based HTML input fields for a view. The default HTML input templates are defined
167+ /// as embedded resources in GSF.Web.
168+ /// </remarks>
169+ public IRazorEngine DataContextRazorEngine
170+ {
171+ get;
172+ set;
173+ }
174+
262175 #endregion
263176
264177 #region [ Methods ]
@@ -269,10 +182,10 @@ namespace GSF.Web.Model · 4 unmodified lines
269182 /// <returns>Rendered result.</returns>
270183 public string Execute()
271184 {
272− using (DataContext<TLanguage> dataContext = new DataContext<TLanguage>(exceptionHandler: ExceptionHandler))
185+ using (DataContext dataContext = new DataContext(Connection, razorEngine: DataContextRazorEngine, exceptionHandler: ExceptionHandler))
273186 {
274187 m_viewBag.AddValue("DataContext", dataContext);
275− return s_engineService.RunCompile(TemplateName, ModelType, Model, m_viewBag);
188+ return m_razorEngine.RunCompile(TemplateName, ModelType, Model, m_viewBag);
276189 }
277190 }
278191
@@ -282,7 +195,7 @@ namespace GSF.Web.Model · 3 unmodified lines
282195 /// <returns>Rendered result.</returns>
283196 public string Execute(HttpRequestMessage requestMessage, dynamic postData)
284197 {
285− using (DataContext<TLanguage> dataContext = new DataContext<TLanguage>(exceptionHandler: ExceptionHandler))
198+ using (DataContext dataContext = new DataContext(Connection, razorEngine: DataContextRazorEngine, exceptionHandler: ExceptionHandler))
286199 {
287200 m_viewBag.AddValue("DataContext", dataContext);
288201 m_viewBag.AddValue("Request", requestMessage);
@@ -297,7 +210,7 @@ namespace GSF.Web.Model · 8 unmodified lines
297210 m_viewBag.AddValue("PostData", postData);
298211 }
299212
300− return s_engineService.RunCompile(TemplateName, ModelType, Model, m_viewBag);
213+ return m_razorEngine.RunCompile(TemplateName, ModelType, Model, m_viewBag);
301214 }
302215 }
303216
@@ -311,141 +224,5 @@ namespace GSF.Web.Model · 7 unmodified lines
311224 }
312225
313226 #endregion
314−
315− #region [ Static ]
316−
317− // Static Fields
318− private static readonly IRazorEngineService s_engineService;
319−
320− /// <summary>
321− /// Defines default settings path for template files.
322− /// </summary>
323− public static readonly string TemplatePath;
324−
325− // Static Constructor
326− static RazorView()
327− {
328− // The current thinking for this class is that there will only be one set of Razor views per application instance
329− TLanguage languageType = new TLanguage();
330−
331− // Get configured template path
332− CategorizedSettingsElementCollection systemSettings = ConfigurationFile.Current.Settings["systemSettings"];
333−
334− switch (languageType.ResolutionMode)
335− {
336− case RazorViewResolutionMode.ResolvePath:
337− case RazorViewResolutionMode.WatchingResolvePath:
338− if (Common.GetApplicationType() == ApplicationType.Web)
339− {
340− systemSettings.Add("TemplatePath", "~/Views/Shared/Templates/", "Path for data context based razor field templates.");
341− TemplatePath = HostingEnvironment.MapPath(systemSettings["TemplatePath"].Value).EnsureEnd('/');
342− }
343− else
344− {
345− systemSettings.Add("TemplatePath", "wwwroot/Templates/", "Path for data context based razor field templates.");
346− TemplatePath = FilePath.GetAbsolutePath(systemSettings["TemplatePath"].Value);
347− }
348−
349− if (languageType.ResolutionMode == RazorViewResolutionMode.ResolvePath)
350− {
351− s_engineService = RazorEngineService.Create(new TemplateServiceConfiguration
352− {
353− Language = languageType.TargetLanguage,
354− TemplateManager = new ResolvePathTemplateManager(new[] { TemplatePath }),
355− Debug = false
356− });
357− }
358− else
359− {
360− // The watching resolve path template manager should not be used in production since
361− // assemblies cannot be unloaded from an AppDomain. Every time a change to a .cshtml
362− // file is picked up by the watcher it is compiled and loaded into the AppDomain and
363− // the old one cannot be removed (.NET restriction), the net result is a memory leak
364− InvalidatingCachingProvider cachingProvider = new InvalidatingCachingProvider();
365−
366− s_engineService = RazorEngineService.Create(new TemplateServiceConfiguration
367− {
368− Language = languageType.TargetLanguage,
369− CachingProvider = cachingProvider,
370− TemplateManager = new WatchingResolvePathTemplateManager(new[] { TemplatePath }, cachingProvider),
371− Debug = true
372− });
373− }
374− break;
375− case RazorViewResolutionMode.EmbeddedResource:
376− systemSettings.Add("EmbeddedTemplatePath", "GSF.Web.Model.Views.", "Embedded name space path for data context based razor field templates.");
377− TemplatePath = systemSettings["EmbeddedTemplatePath"].Value.EnsureEnd('.');
378−
379− s_engineService = RazorEngineService.Create(new TemplateServiceConfiguration
380− {
381− Language = languageType.TargetLanguage,
382− TemplateManager = new DelegateTemplateManager(name =>
383− {
384− string resourceName = name;
385−
386− if (!resourceName.Contains("."))
387− resourceName = TemplatePath + name;
388−
389− Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName);
390−
391− if ((object)stream == null)
392− return "";
393−
394− using (StreamReader reader = new StreamReader(stream))
395− return reader.ReadToEnd();
396− }),
397− Debug = false
398− });
399− break;
400− }
401− }
402−
403− /// <summary>
404− /// Kicks off a task to pre-compile Razor templates.
405− /// </summary>
406− /// <param name="exceptionHandler">Exception handler used to report issues, if any.</param>
407− public static Task PreCompile(Action<Exception> exceptionHandler = null)
408− {
409− return Task.Run(() =>
410− {
411− TLanguage languageType = new TLanguage();
412−
413− if (languageType.ResolutionMode == RazorViewResolutionMode.EmbeddedResource)
414− {
415− foreach (string fileName in Assembly.GetExecutingAssembly().GetManifestResourceNames().Where(fileName => fileName.StartsWith(TemplatePath)))
416− {
417− try
418− {
419− s_engineService.Compile(fileName.Substring(TemplatePath.Length));
420− }
421− catch (Exception ex)
422− {
423− if ((object)exceptionHandler != null)
424− exceptionHandler(new InvalidOperationException($"Failed to pre-compile razor template \"{fileName}\": {ex.Message}", ex));
425− }
426− }
427− }
428− else
429− {
430− string webRootFolder = FilePath.AddPathSuffix(TemplatePath);
431− string[] razorFiles = FilePath.GetFileList($"{webRootFolder}*.{(languageType.TargetLanguage == Language.CSharp ? "cs" : "vb")}html");
432−
433− foreach (string fileName in razorFiles)
434− {
435− try
436− {
437− s_engineService.Compile(FilePath.GetFileName(fileName));
438− }
439− catch (Exception ex)
440− {
441− if ((object)exceptionHandler != null)
442− exceptionHandler(new InvalidOperationException($"Failed to pre-compile razor template \"{fileName}\": {ex.Message}", ex));
443− }
444− }
445− }
446− });
447− }
448−
449− #endregion
450227 }
451228 }
Source/Libraries/GSF.Web/Model/RazorViewResolutionMode.cs+47−0
@@ -0,0 +1,47 @@
1+//******************************************************************************************************
2+// RazorViewResolutionMode.cs - Gbtc
3+//
4+// Copyright � 2016, Grid Protection Alliance. All Rights Reserved.
5+//
6+// Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See
7+// the NOTICE file distributed with this work for additional information regarding copyright ownership.
8+// The GPA licenses this file to you under the MIT License (MIT), the "License"; you may not use this
9+// file except in compliance with the License. You may obtain a copy of the License at:
10+//
11+// http://opensource.org/licenses/MIT
12+//
13+// Unless agreed to in writing, the subject software distributed under the License is distributed on an
14+// "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the
15+// License for the specific language governing permissions and limitations.
16+//
17+// Code Modification History:
18+// ----------------------------------------------------------------------------------------------------
19+// 05/05/2016 - J. Ritchie Carroll
20+// Generated original version of source code.
21+//
22+//******************************************************************************************************
23+
24+namespace GSF.Web.Model
25+{
26+ /// <summary>
27+ /// Defines resolutions modes for Razor view template files.
28+ /// </summary>
29+ public enum RazorViewResolutionMode
30+ {
31+ /// <summary>
32+ /// Resolves template files from a path.
33+ /// </summary>
34+ ResolvePath,
35+ /// <summary>
36+ /// Resolves template files from a path and watches for changes.
37+ /// </summary>
38+ /// <remarks>
39+ /// This should only be used in debug mode.
40+ /// </remarks>
41+ WatchingResolvePath,
42+ /// <summary>
43+ /// Resolves template files from an embedded resource.
44+ /// </summary>
45+ EmbeddedResource
46+ }
47+}

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