This is an initial version of RazorEngine generator. It's very similar to RazorGenerator but instead of generating code which is ASP.NET MVC specific (you can not use it to render an email template outside of http context in windows service or windows application) it generates code which uses RazorEngine infrastructure, which is well suited for the scenario above. This tool is based on a fork of RazorEngine with come customizations. The code can be found here -https://github.com/zihotki/RazorEngine/tree/razorgenerator But currently it's code resides in RazorEngine solution. That's because this extension modifies some parts of RazorEngine to get generated code. Contributing the changes back to the RazorEngine makes no sense since they've switched to Roslyn based generation and updating the extension to the latest version of RazorEngine also won't give any benefits since Razor syntax didn't change and the tool works fine.
RazorEngine is a great library but, unfortunately, sometimes it could fail to compile a template at runtime due to a bug in CodeDom compiler which is used by Razor to generate and compile code. And you have to restart the service or recycle the app pool to make it working again. That error is very rare but in our case we've seen it too often and there were no reliable solutions except for using some tool to pregenerate C# code. That could work only when you don't create the template dynamically at runtime which is our case. I did some googling for a tool which would allow us to pregenerate C# for Razor templates and I found a few but all of them were using standard ASP.NET MVC infrastructure and it was very hard to make them working outside of web application. So I decided to implement a custom generator tool based on RazorEngine infrastructure which works mostly well outside of web apps. To use it you should first install the tool, then add RazorEngine nuget package as well as Microsoft ASP.NET MVC, and then create a standard cshtml template in your project and setCustom Tool in file properties to RazorEngineGenerator:
Then copy this code or make your own implementation of RazorEngine.Templating.ITemplateService: C# Edit|Remove csharpusing System;using System.Collections.Generic;using System.Linq;using System.Reflection;using System.Web.Razor.Parser;using RazorEngine.Configuration;using RazorEngine.Templating;using RazorEngine.Text;namespace Templates{ public class NotificationTemplateService : ITemplateService { private readonly Dictionary<string, Type> _viewsRegistry = new Dictionary<string, Type>(); private readonly IEncodedStringFactory _encodedStringFactory = new HtmlEncodedStringFactory(); public void RegisterRazorEngineViews(Assembly assembly, string namespacePrefix) { // scanning the assembly for all public types derived from ITemplate and adding them into cache var templateType = typeof(ITemplate); var views = assembly.GetExportedTypes().Where(x => templateType.IsAssignableFrom(x)); namespacePrefix += "."; foreach (var view in views) { // full name of a class like Namespace.Prefix.Foo.Bar.Baz will be translated to foo_bar_baz which // is the unique name for a template var name = ParserHelpers.SanitizeClassName(view.FullName.Replace(namespacePrefix, "")).ToLowerInvariant(); _viewsRegistry.Add(name, view); } } public ITemplate Resolve(string templateUrl, object model) { var name = ConvertPathToName(templateUrl); if (_viewsRegistry.ContainsKey(name) == false) { var exception = new Exception(string.Format("Template not registered. Template url is '{0}', resolved name is '{1}'.", templateUrl, name)); throw exception; } var view = _viewsRegistry[name]; var instance = (ITemplate)Activator.CreateInstance(view); instance.TemplateService = this; SetModel(instance, model); return instance; } public string Run(string templateUrl, object model, DynamicViewBag viewBag) { var template = Resolve(templateUrl, model); return template.Run(CreateExecuteContext(viewBag)); } public ExecuteContext CreateExecuteContext(DynamicViewBag viewBag = null) { return new ExecuteContext(viewBag); } public IEncodedStringFactory EncodedStringFactory { get { return _encodedStringFactory; } } public void Dispose() { // nothing to do } #region Copied from RazorEngine's TemplateService private static void SetModel<T>(ITemplate template, T model) { if (model == null) return; var template1 = template as ITemplate<object>; if (template1 != null) { template1.Model = model; } else { var template2 = template as ITemplate<T>; if (template2 != null) { template2.Model = model; } else { SetModelExplicit(template, model); } } } private static void SetModelExplicit(ITemplate template, object model) { var property = template.GetType().GetProperty("Model"); if (!(property != null)) return; property.SetValue(template, model, null); } #endregion private string ConvertPathToName(string templatePath) { // this will translate path like ~/Foo/Bar/Baz.cshtml to foo_bar_baz which is the key we used to save view type in cache templatePath = templatePath .Replace("~/", "") .Replace("/", ".") .Replace(".cshtml", ""); return ParserHelpers.SanitizeClassName(templatePath).ToLowerInvariant(); } #region Stubs public void AddNamespace(string ns) { throw new NotImplementedException(); } public void Compile(string razorTemplate, Type modelType, string cacheName) { throw new NotImplementedException(); } public ITemplate CreateTemplate(string razorTemplate, Type templateType, object model) { throw new NotImplementedException(); } public IEnumerable<ITemplate> CreateTemplates(IEnumerable<string> razorTemplates, IEnumerable<Type> templateTypes, IEnumerable<object> models, bool parallel = false) { throw new NotImplementedException(); } public Type CreateTemplateType(string razorTemplate, Type modelType) { throw new NotImplementedException(); } public IEnumerable<Type> CreateTemplateTypes(IEnumerable<string> razorTemplates, IEnumerable<Type> modelTypes, bool parallel = false) { throw new NotImplementedException(); } public ITemplate GetTemplate(string razorTemplate, object model, string cacheName) { throw new NotImplementedException(); } public IEnumerable<ITemplate> GetTemplates(IEnumerable<string> razorTemplates, IEnumerable<object> models, IEnumerable<string> cacheNames, bool parallel = false) { throw new NotImplementedException(); } public bool HasTemplate(string cacheName) { throw new NotImplementedException(); } public bool RemoveTemplate(string cacheName) { throw new NotImplementedException(); } public string Parse(string razorTemplate, object model, DynamicViewBag viewBag, string cacheName) { throw new NotImplementedException(); } public string Parse<T>(string razorTemplate, object model, DynamicViewBag viewBag, string cacheName) { throw new NotImplementedException(); } public IEnumerable<string> ParseMany(IEnumerable<string> razorTemplates, IEnumerable<object> models, IEnumerable<DynamicViewBag> viewBags, IEnumerable<string> cacheNames, bool parallel) { throw new NotImplementedException(); } public string Run(ITemplate template, DynamicViewBag viewBag) { throw new NotImplementedException(); } public ITemplateServiceConfiguration Configuration { get { throw new NotImplementedException(); } } #endregion }} using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Web.Razor.Parser; using RazorEngine.Configuration; using RazorEngine.Templating; using RazorEngine.Text; namespace Templates { public class NotificationTemplateService : ITemplateService { private readonly Dictionary<string, Type> _viewsRegistry = new Dictionary<string, Type>(); private readonly IEncodedStringFactory _encodedStringFactory = new HtmlEncodedStringFactory(); public void RegisterRazorEngineViews(Assembly assembly, string namespacePrefix) { // scanning the assembly for all public types derived from ITemplate and adding them into cache var templateType = typeof(ITemplate); var views = assembly.GetExportedTypes().Where(x => templateType.IsAssignableFrom(x)); namespacePrefix += "."; foreach (var view in views) { // full name of a class like Namespace.Prefix.Foo.Bar.Baz will be translated to foo_bar_baz which // is the unique name for a template var name = ParserHelpers.SanitizeClassName(view.FullName.Replace(namespacePrefix, "")).ToLowerInvariant(); _viewsRegistry.Add(name, view); } } public ITemplate Resolve(string templateUrl, object model) { var name = ConvertPathToName(templateUrl); if (_viewsRegistry.ContainsKey(name) == false) { var exception = new Exception(string.Format("Template not registered. Template url is '{0}', resolved name is '{1}'.", templateUrl, name)); throw exception; } var view = _viewsRegistry[name]; var instance = (ITemplate)Activator.CreateInstance(view); instance.TemplateService = this; SetModel(instance, model); return instance; } public string Run(string templateUrl, object model, DynamicViewBag viewBag) { var template = Resolve(templateUrl, model); return template.Run(CreateExecuteContext(viewBag)); } public ExecuteContext CreateExecuteContext(DynamicViewBag viewBag = null) { return new ExecuteContext(viewBag); } public IEncodedStringFactory EncodedStringFactory { get { return _encodedStringFactory; } } public void Dispose() { // nothing to do } #region Copied from RazorEngine's TemplateService private static void SetModel<T>(ITemplate template, T model) { if (model == null) return; var template1 = template as ITemplate<object>; if (template1 != null) { template1.Model = model; } else { var template2 = template as ITemplate<T>; if (template2 != null) { template2.Model = model; } else { SetModelExplicit(template, model); } } } private static void SetModelExplicit(ITemplate template, object model) { var property = template.GetType().GetProperty("Model"); if (!(property != null)) return; property.SetValue(template, model, null); } #endregion private string ConvertPathToName(string templatePath) { // this will translate path like ~/Foo/Bar/Baz.cshtml to foo_bar_baz which is the key we used to save view type in cache templatePath = templatePath .Replace("~/", "") .Replace("/", ".") .Replace(".cshtml", ""); return ParserHelpers.SanitizeClassName(templatePath).ToLowerInvariant(); } #region Stubs public void AddNamespace(string ns) { throw new NotImplementedException(); } public void Compile(string razorTemplate, Type modelType, string cacheName) { throw new NotImplementedException(); } public ITemplate CreateTemplate(string razorTemplate, Type templateType, object model) { throw new NotImplementedException(); } public IEnumerable<ITemplate> CreateTemplates(IEnumerable<string> razorTemplates, IEnumerable<Type> templateTypes, IEnumerable<object> models, bool parallel = false) { throw new NotImplementedException(); } public Type CreateTemplateType(string razorTemplate, Type modelType) { throw new NotImplementedException(); } public IEnumerable<Type> CreateTemplateTypes(IEnumerable<string> razorTemplates, IEnumerable<Type> modelTypes, bool parallel = false) { throw new NotImplementedException(); } public ITemplate GetTemplate(string razorTemplate, object model, string cacheName) { throw new NotImplementedException(); } public IEnumerable<ITemplate> GetTemplates(IEnumerable<string> razorTemplates, IEnumerable<object> models, IEnumerable<string> cacheNames, bool parallel = false) { throw new NotImplementedException(); } public bool HasTemplate(string cacheName) { throw new NotImplementedException(); } public bool RemoveTemplate(string cacheName) { throw new NotImplementedException(); } public string Parse(string razorTemplate, object model, DynamicViewBag viewBag, string cacheName) { throw new NotImplementedException(); } public string Parse<T>(string razorTemplate, object model, DynamicViewBag viewBag, string cacheName) { throw new NotImplementedException(); } public IEnumerable<string> ParseMany(IEnumerable<string> razorTemplates, IEnumerable<object> models, IEnumerable<DynamicViewBag> viewBags, IEnumerable<string> cacheNames, bool parallel) { throw new NotImplementedException(); } public string Run(ITemplate template, DynamicViewBag viewBag) { throw new NotImplementedException(); } public ITemplateServiceConfiguration Configuration { get { throw new NotImplementedException(); } } #endregion } } And use it: C# Edit|Remove csharpstatic void Main(string[] args) { var templateService = new NotificationTemplateService(); templateService.RegisterRazorEngineViews(typeof(ViewPage1).Assembly, typeof(ViewPage1).Assembly.GetName().Name); var content = templateService.Run("~/Views/Welcome/ViewPage1.cshtml", new HelloViewModel { FirstName = "Peter", LastName = "Omaz" }, null); Console.WriteLine(content); Console.ReadKey(); } static void Main(string[] args) { var templateService = new NotificationTemplateService(); templateService.RegisterRazorEngineViews(typeof(ViewPage1).Assembly, typeof(ViewPage1).Assembly.GetName().Name); var content = templateService.Run("~/Views/Welcome/ViewPage1.cshtml", new HelloViewModel { FirstName = "Peter", LastName = "Omaz" }, null); Console.WriteLine(content); Console.ReadKey(); } If you need to use some custom view template you can set it in web.config just like you do that for ASP.NET MVC projects. The extension will search parent directories for files with name "web.config" up to the folder with the project or solution file ("*.csproj" or "*.sln"). If no configs are present the default values will be used. A full example could be found here - https://github.com/zihotki/RazorEngine.Generator.Sample Pros and cons of this approach:
|