Rick Strahl's Weblog  

Wind, waves, code and everything in between...
.NET • C# • Markdown • WPF • All Things Web
Contact   •   Articles   •   Products   •   Support   •   Advertise
Sponsored by:
Markdown Monster - The Markdown Editor for Windows

Hosting the Razor Engine for Templating in Non-Web Applications


:P
On this page:

Microsoft’s new Razor HTML Rendering Engine that is currently shipping with ASP.NET MVC previews can be used outside of ASP.NET. Razor is an alternative view engine that can be used instead of the ASP.NET Page engine that currently works with ASP.NET WebForms and MVC. It provides a simpler and more readable markup syntax and is much more light weight in terms of functionality than the full blown WebForms Page engine, focusing only on features that are more along the lines of a pure view engine (or classic ASP!) with focus on expression and code rendering rather than a complex control/object model. Like the Page engine though, the parser understands .NET code syntax which can be embedded into templates, and behind the scenes the engine compiles markup and script code into an executing piece of .NET code in an assembly.

Although it ships as part of the ASP.NET MVC and WebMatrix the Razor Engine itself is not directly dependent on ASP.NET or IIS or HTTP in any way. And although there are some markup and rendering features that are optimized for HTML based output generation, Razor is essentially a free standing template engine. And what’s really nice is that unlike the ASP.NET Runtime, Razor is fairly easy to host inside of your own non-Web applications to provide templating functionality.

Templating in non-Web Applications? Yes please!

So why might you host a template engine in your non-Web application? Template rendering is useful in many places and I have a number of applications that make heavy use of it. One of my applications – West Wind Html Help Builder - exclusively uses template based rendering to merge user supplied help text content into customizable and executable HTML markup templates that provide HTML output for CHM style HTML Help. This is an older product and it’s not actually using .NET at the moment – and this is one reason I’m looking at Razor for script hosting at the moment.

For a few .NET applications though I’ve actually used the ASP.NET Runtime hosting to provide templating and mail merge style functionality and while that works reasonably well it’s a very heavy handed approach. It’s very resource intensive and has potential issues with versioning in various different versions of .NET. The generic implementation I created in the article above requires a lot of fix up to mimic an HTTP request in a non-HTTP environment and there are a lot of little things that have to happen to ensure that the ASP.NET runtime works properly most of it having nothing to do with the templating aspect but just satisfying ASP.NET’s requirements.

The Razor Engine on the other hand is fairly light weight and completely decoupled from the ASP.NET runtime and the HTTP processing. Rather it’s a pure template engine whose sole purpose is to render text templates. Hosting this engine in your own applications can be accomplished with a reasonable amount of code (actually just a few lines with the tools I’m about to describe) and without having to fake HTTP requests. It’s also much lighter on resource usage and you can easily attach custom properties to your base template implementation to easily pass context from the parent application into templates all of which was rather complicated with ASP.NET runtime hosting.

Installing the Razor Template Engine

You can get Razor as part of the MVC 3 (RC and later) or Web Matrix. Both are available as downloadable components from the Web Platform Installer Version 3.0 (!important – V2 doesn’t show these components). If you already have that version of the WPI installed just fire it up. You can get the latest version of the Web Platform Installer from here:

http://www.microsoft.com/web/gallery/install.aspx

Once the platform Installer 3.0 is installed install either MVC 3 or ASP.NET Web Pages. Once installed you’ll find a System.Web.Razor assembly in C:\Program Files\Microsoft ASP.NET\ASP.NET Web Pages\v1.0\Assemblies\System.Web.Razor.dll which you can add as a reference to your project.

Creating a Wrapper

The basic Razor Hosting API is pretty simple and you can host Razor with a (large-ish) handful of lines of code. I’ll show the basics of it later in this article. However, if you want to customize the rendering and handle assembly and namespace includes for the markup as well as deal with text and file inputs as well as forcing Razor to run in a separate AppDomain so you can unload the code-generated assemblies and deal with assembly caching for re-used templates little more work is required to create something that is more easily reusable.

For this reason I created a Razor Hosting wrapper project that combines a bunch of this functionality into an easy to use hosting class, a hosting factory that can load the engine in a separate AppDomain and a couple of hosting containers that provided folder based and string based caching for templates for an easily embeddable and reusable engine with easy to use syntax.

If you just want the code and play with the samples and source go grab the latest code from the Subversion Repository at:

http://www.west-wind.com:8080/svn/articles/trunk/RazorHosting/

or a snapshot from:

http://www.west-wind.com/files/tools/RazorHosting.zip

Getting Started

Before I get into how hosting with Razor works, let’s take a look at how you can get up and running quickly with the wrapper classes provided. It only takes a few lines of code. Let’s start with a very simple template that displays some simple expressions, some code blocks and demonstrates rendering some data from contextual data that you pass to the template in the form of a ‘context’.

Here’s a simple Razor template:

@inherits RazorHosting.RazorTemplateBase
@using System.Reflection
Hello @Context.FirstName! Your entry was entered on: @Context.Entered

@{
    // Code block: Update the host Windows Form passed in through the context
    Context.WinForm.Text = "Hello World from Razor at " + DateTime.Now.ToString();
}

AppDomain Id:
    @AppDomain.CurrentDomain.FriendlyName
            
Assembly:
    @Assembly.GetExecutingAssembly().FullName

Code based output: 
@{
    // Write output with Response object from code
    string output = string.Empty;
    for (int i = 0; i < 10; i++)
    {
        output += i.ToString() + " ";
    }
    Response.Write(output);
}

Note: the @inherits above provides a way to override the default template type used by Razor and this wrapper. It’s optional, but it also provides Intellisense for your custom template in Visual Studio so it’s a good idea to provide the type in the template itself.

Pretty easy to see what’s going on here. The only unusual thing in this code is the Context object which is an arbitrary object I’m passing from the host to the template by way of the template base class. I’m also displaying the current AppDomain and the executing Assembly name so you can see how compiling and running a template actually loads up new assemblies. Also note that as part of my context I’m passing a reference to the current Windows Form down to the template and changing the title from within the script. It’s a silly example, but it demonstrates two-way communication between host and template and back which can be very powerful.

The easiest way to quickly render this template is to use the RazorEngine<TTemplateBase> class. The generic parameter specifies a template base class type that is used by Razor internally to generate the class it generates from a template. The default implementation provided in my RazorHosting wrapper is RazorTemplateBase.

Here’s a simple example that renders from a string and outputs a string:

var engine = new RazorEngine<RazorTemplateBase>();

// we can pass any object as context - here create a custom context
var context = new CustomContext()
{
    WinForm = this,
FirstName = "Rick",
Entered = DateTime.Now.AddDays(-10) }; string output = engine.RenderTemplate(this.txtSource.Text
new string[] { "System.Windows.Forms.dll" },
context); if (output == null) this.txtResult.Text = "*** ERROR:\r\n" + engine.ErrorMessage; else this.txtResult.Text = output;

Simple enough. This code renders a template from a string input and returns a result back as a string. It  creates a custom context and passes that to the template which can then access the Context’s properties. Note that anything passed as ‘context’ must be serializable (or MarshalByRefObject) – otherwise you get an exception when passing the reference over AppDomain boundaries (discussed later). Passing a context is optional, but is a key feature in being able to share data between the host application and the template. Note that we use the Context object to access FirstName, Entered and even the host Windows Form object which is used in the template to change the Window caption from within the script!

In the code above all the work happens in the RenderTemplate method which provide a variety of overloads to read and write to and from strings, files and TextReaders/Writers. Here’s another example that renders from a file input using a TextReader:

using (reader = new StreamReader("templates\\simple.csHtml", true))
{
    result = host.RenderTemplate(reader,  new string[] { "System.Windows.Forms.dll" }, 
this.CustomContext); }

RenderTemplate() is fairly high level and it handles loading of the runtime, compiling into an assembly and rendering of the template. If you want more control you can use the lower level methods to control each step of the way which is important for the HostContainers I’ll discuss later. Basically for those scenarios you want to separate out loading of the engine, compiling into an assembly and then rendering the template from the assembly. Why? So we can keep assemblies cached. In the code above a new assembly is created for each template rendered which is inefficient and uses up resources. Depending on the size of your templates and how often you fire them you can chew through memory very quickly.

This slighter lower level approach is only a couple of extra steps:

// we can pass any object as context - here create a custom context
var context = new CustomContext()
{
    WinForm = this,
FirstName = "Rick", Entered = DateTime.Now.AddDays(-10) }; var engine = new RazorEngine<RazorTemplateBase>(); string assId = null; using (StringReader reader = new StringReader(this.txtSource.Text)) { assId = engine.ParseAndCompileTemplate(new string[] { "System.Windows.Forms.dll" }, reader); } string output = engine.RenderTemplateFromAssembly(assId, context); if (output == null) this.txtResult.Text = "*** ERROR:\r\n" + engine.ErrorMessage; else this.txtResult.Text = output;

The difference here is that you can capture the assembly – or rather an Id to it – and potentially hold on to it to render again later assuming the template hasn’t changed. The HostContainers take advantage of this feature to cache the assemblies based on certain criteria like a filename and file time step or a string hash that if not change indicate that an assembly can be reused.

Note that ParseAndCompileTemplate returns an assembly Id rather than the assembly itself. This is done so that that the assembly always stays in the host’s AppDomain and is not passed across AppDomain boundaries which would cause load failures. We’ll talk more about this in a minute but for now just realize that assemblies references are stored in a list and are accessible by this ID to allow locating and re-executing of the assembly based on that id. Reuse of the assembly avoids recompilation overhead and creation of yet another assembly that loads into the current AppDomain.

You can play around with several different versions of the above code in the main sample form:

RazorSampleForm 

Using Hosting Containers for more Control and Caching

The above examples simply render templates into assemblies each and every time they are executed. While this works and is even reasonably fast, it’s not terribly efficient. If you render templates more than once it would be nice if you could cache the generated assemblies for example to avoid re-compiling and creating of a new assembly each time. Additionally it would be nice to load template assemblies into a separate AppDomain optionally to be able to be able to unload assembli es and also to protect your host application from scripting attacks with malicious template code. Hosting containers provide also provide a wrapper around the RazorEngine<T> instance, a factory (which allows creation in separate AppDomains) and an easy way to start and stop the container ‘runtime’.

The Razor Hosting samples provide two hosting containers: RazorFolderHostContainer and StringHostContainer. The folder host provides a simple runtime environment for a folder structure similar in the way that the ASP.NET runtime handles a virtual directory as it’s ‘application' root. Templates are loaded from disk in relative paths and the resulting assemblies are cached unless the template on disk is changed. The string host also caches templates based on string hashes – if the same string is passed a second time a cached version of the assembly is used.

Here’s how HostContainers work. I’ll use the FolderHostContainer because it’s likely the most common way you’d use templates – from disk based templates that can be easily edited and maintained on disk. The first step is to create an instance of it and keep it around somewhere (in the example it’s attached as a property to the Form):

RazorFolderHostContainer Host = new RazorFolderHostContainer();
public RazorFolderHostForm()
{
    InitializeComponent();
    
    // The base path for templates - templates are rendered with relative paths
    // based on this path.
    Host.TemplatePath = Path.Combine(Environment.CurrentDirectory, TemplateBaseFolder); 

    // Add any assemblies you want reference in your templates
    Host.ReferencedAssemblies.Add("System.Windows.Forms.dll");
    
    // Start up the host container
    Host.Start();
}

Next anytime you want to render a template you can use simple code like this:

private void RenderTemplate(string fileName)
{
    // Pass the template path via the Context            
    var relativePath = Utilities.GetRelativePath(fileName, Host.TemplatePath);

    if (!Host.RenderTemplate(relativePath, this.Context, Host.RenderingOutputFile))
    {
        MessageBox.Show("Error: " + Host.ErrorMessage);
        return;
    }

    this.webBrowser1.Navigate("file://" + Host.RenderingOutputFile);
}

You can also render the output to a string instead of to a file:

string result = Host.RenderTemplateToString(relativePath,context);

Finally if you want to release the engine and shut down the hosting AppDomain you can simply do:

Host.Stop();

Stopping the AppDomain and restarting it (ie. calling Stop(); followed by Start()) is also a nice way to release all resources in the AppDomain.

The FolderBased domain also supports partial Rendering based on root path based relative paths with the same caching characteristics as the main templates. From within a template you can call out to a partial like this:

@RenderPartial(@"partials\PartialRendering.cshtml", Context)

where partials\PartialRendering.cshtml is a relative to the template root folder. The folder host example lets you load up templates from disk and display the result in a Web Browser control which demonstrates using Razor HTML output from templates that contain HTML syntax which happens to me my target scenario for Html Help Builder.

folderhostsample[4] 

The Razor Engine Wrapper Project

The project I created to wrap Razor hosting has a fair bit of code and a number of classes associated with it. Most of the components are internally used and as you can see using the final RazorEngine<T> and HostContainer classes is pretty easy. The classes are extensible and I suspect developers will want to build more customized host containers for their applications. Host containers are the key to wrapping up all functionality – Engine, BaseTemplate, AppDomain Hosting, Caching etc in a logical piece that is ready to be plugged into an application.

When looking at the code there are a couple of core features provided:

  • Core Razor Engine Hosting
    This is the core Razor hosting which provides the basics of loading a template, compiling it into an assembly and executing it. This is fairly straightforward, but without a host container that can cache assemblies based on some criteria templates are recompiled and re-created each time which is inefficient (although pretty fast). The base engine wrapper implementation also supports hosting the Razor runtime in a separate AppDomain for security and the ability to unload it on demand.
  • Host Containers
    The engine hosting itself doesn’t provide any sort of ‘runtime’ service like picking up files from disk, caching assemblies and so forth. So my implementation provides two HostContainers: RazorFolderHostContainer and RazorStringHostContainer. The FolderHost works off a base directory and loads templates based on relative paths (sort of like the ASP.NET runtime does off a virtual). The HostContainers also deal with caching of template assemblies – for the folder host the file date is tracked and checked for updates and unless the template is changed a cached assembly is reused. The StringHostContainer similiarily checks string hashes to figure out whether a particular string template was previously compiled and executed. The HostContainers also act as a simple startup environment and a single reference to easily store and reuse in an application.
  • TemplateBase Classes
    The template base classes are the base classes that from which the Razor engine generates .NET code. A template is parsed into a class with an Execute() method and the class is based on this template type you can specify. RazorEngine<TBaseTemplate> can receive this type and the HostContainers default to specific templates in their base implementations. Template classes are customizable to allow you to create templates that provide application specific features and interaction from the template to your host application.

    How does the RazorEngine wrapper work?

    You can browse the source code in the links above or in the repository or download the source, but I’ll highlight some key features here. Here’s part of the RazorEngine implementation that can be used to host the runtime and that demonstrates the key code required to host the Razor runtime.

    The RazorEngine class is implemented as a generic class to reflect the Template base class type:

       public class RazorEngine<TBaseTemplateType> : MarshalByRefObject
            where TBaseTemplateType : RazorTemplateBase
    

    The generic type is used to internally provide easier access to the template type and assignments on it as part of the template processing. As mentioned earlier the actual type of the page may be overridden by the template via the @inherits keyword – the generic type provides the default type used if it’s not provided in the template itself.

    The class also inherits MarshalByRefObject to allow execution over AppDomain boundaries – something that all the classes discussed here need to do since there is much interaction between the host and the template.

    The first two key methods deal with creating a template assembly:

    /// <summary>
    /// Creates an instance of the RazorHost with various options applied.
    /// Applies basic namespace imports and the name of the class to generate
    /// </summary>
    /// <param name="generatedNamespace"></param>
    /// <param name="generatedClass"></param>
    /// <returns></returns>
    protected RazorTemplateEngine CreateHost(string generatedNamespace, string generatedClass)
    {     
        Type baseClassType = typeof(TBaseTemplateType);
    
        RazorEngineHost host = new RazorEngineHost(new CSharpRazorCodeLanguage());
        host.DefaultBaseClass = baseClassType.FullName;
        host.DefaultClassName = generatedClass;
        host.DefaultNamespace = generatedNamespace;
        host.NamespaceImports.Add("System");
        host.NamespaceImports.Add("System.Text");
        host.NamespaceImports.Add("System.Collections.Generic");
        host.NamespaceImports.Add("System.Linq");
        host.NamespaceImports.Add("System.IO");
        
        return new RazorTemplateEngine(host);            
    }
    
    
    /// <summary>
    /// Parses and compiles a markup template into an assembly and returns
    /// an assembly name. The name is an ID that can be passed to 
    /// ExecuteTemplateByAssembly which picks up a cached instance of the
    /// loaded assembly.
    /// 
    /// </summary>
    /// <param name="namespaceOfGeneratedClass">The namespace of the class to generate from the template</param>
    /// <param name="generatedClassName">The name of the class to generate from the template</param>
    /// <param name="ReferencedAssemblies">Any referenced assemblies by dll name only. Assemblies must be in execution path of host or in GAC.</param>
    /// <param name="templateSourceReader">Textreader that loads the template</param>
    /// <remarks>
    /// The actual assembly isn't returned here to allow for cross-AppDomain
    /// operation. If the assembly was returned it would fail for cross-AppDomain
    /// calls.
    /// </remarks>
    /// <returns>An assembly Id. The Assembly is cached in memory and can be used with RenderFromAssembly.</returns>
    public string ParseAndCompileTemplate(
                string namespaceOfGeneratedClass,
                string generatedClassName,
                string[] ReferencedAssemblies,
                TextReader templateSourceReader)
    {
        RazorTemplateEngine engine = CreateHost(namespaceOfGeneratedClass, generatedClassName);
    
        // Generate the template class as CodeDom  
        GeneratorResults razorResults = engine.GenerateCode(templateSourceReader);
                    
        // Create code from the codeDom and compile
        CSharpCodeProvider codeProvider = new CSharpCodeProvider();
        CodeGeneratorOptions options = new CodeGeneratorOptions();
    
        // Capture Code Generated as a string for error info
        // and debugging
        LastGeneratedCode = null;
        using (StringWriter writer = new StringWriter())
        {
            codeProvider.GenerateCodeFromCompileUnit(razorResults.GeneratedCode, writer, options);
            LastGeneratedCode = writer.ToString();                
        }
        
        CompilerParameters compilerParameters = new CompilerParameters(ReferencedAssemblies);
        
        // Standard Assembly References
        compilerParameters.ReferencedAssemblies.Add("System.dll");
        compilerParameters.ReferencedAssemblies.Add("System.Core.dll");
        compilerParameters.ReferencedAssemblies.Add("Microsoft.CSharp.dll");   // dynamic support!                     
    
        // Also add the current assembly so RazorTemplateBase is available
        compilerParameters.ReferencedAssemblies.Add(Assembly.GetExecutingAssembly().CodeBase.Substring(8));
    
        compilerParameters.GenerateInMemory = Configuration.CompileToMemory;
        if (!Configuration.CompileToMemory)
            compilerParameters.OutputAssembly = Path.Combine(Configuration.TempAssemblyPath, "_" 
    + Guid.NewGuid().ToString("n") + ".dll"); CompilerResults compilerResults = codeProvider.CompileAssemblyFromDom(compilerParameters,
    razorResults.GeneratedCode); if (compilerResults.Errors.Count > 0) { var compileErrors = new StringBuilder(); foreach (System.CodeDom.Compiler.CompilerError compileError in compilerResults.Errors) compileErrors.Append(String.Format(Resources.LineX0TColX1TErrorX2RN,
    compileError.Line, compileError.Column, compileError.ErrorText)); this.SetError(compileErrors.ToString() + "\r\n" + LastGeneratedCode); return null; } AssemblyCache.Add(compilerResults.CompiledAssembly.FullName, compilerResults.CompiledAssembly); return compilerResults.CompiledAssembly.FullName; }

    Think of the internal CreateHost() method as setting up the assembly generated from each template. Each template compiles into a separate assembly. It sets up namespaces, and assembly references, the base class used and the name and namespace for the generated class. ParseAndCompileTemplate() then calls the CreateHost() method to receive the template engine generator which effectively generates a CodeDom from the template – the template is turned into .NET code. The code generated from our earlier example looks something like this:
    //------------------------------------------------------------------------------
    // <auto-generated>
    //     This code was generated by a tool.
    //     Runtime Version:4.0.30319.1
    //
    //     Changes to this file may cause incorrect behavior and will be lost if
    //     the code is regenerated.
    // </auto-generated>
    //------------------------------------------------------------------------------
    namespace RazorTest
    {
        using System;
        using System.Text;
        using System.Collections.Generic;
        using System.Linq;
        using System.IO;
        using System.Reflection;
    
        public class RazorTemplate : RazorHosting.RazorTemplateBase
        {
    
    #line hidden
    
            public RazorTemplate()
            {
            }
    
            public override void Execute()
            {
    
                WriteLiteral("Hello ");
    
    
                Write(Context.FirstName);
    
                WriteLiteral("! Your entry was entered on: ");
    
    
                Write(Context.Entered);
    
                WriteLiteral("\r\n\r\n");
    
    
    
                // Code block: Update the host Windows Form passed in through the context
                Context.WinForm.Text = "Hello World from Razor at " + DateTime.Now.ToString();
    
    
                WriteLiteral("\r\nAppDomain Id:\r\n    ");
    
    
                Write(AppDomain.CurrentDomain.FriendlyName);
    
                WriteLiteral("\r\n            \r\nAssembly:\r\n    ");
    
    
                Write(Assembly.GetExecutingAssembly().FullName);
    
                WriteLiteral("\r\n\r\nCode based output: \r\n");
    
    
    
                // Write output with Response object from code
                string output = string.Empty;
                for (int i = 0; i < 10; i++)
                {
                    output += i.ToString() + " ";
                }
            }
        }
    }

    Basically the template’s body is turned into code in an Execute method that is called. Internally the template’s Write method is fired to actually generate the output. Note that the class inherits from RazorTemplateBase which is the generic parameter I used to specify the base class when creating an instance in my RazorEngine host:

    var engine = new RazorEngine<RazorTemplateBase>();

    This template class must be provided and it must implement an Execute() and Write() method. Beyond that you can create any class you chose and attach your own properties. My RazorTemplateBase class implementation is very simple:

    public class RazorTemplateBase : MarshalByRefObject, IDisposable
    {
        
        /// <summary>
        /// You can pass in a generic context object
        /// to use in your template code
        /// </summary>
        public dynamic Context { get; set; }
    
    
        /// <summary>
        /// Class that generates output. Currently ultra simple
        /// with only Response.Write() implementation.
        /// </summary>
        public RazorResponse Response { get; set; }
    
        public object HostContainer {get; set; }
        public object Engine { get; set; }
    
                    
        public RazorTemplateBase()
        {
            Response = new RazorResponse();
        }
    
        public virtual void Write(object value)
        {
            Response.Write(value);
        }
    
        public virtual void WriteLiteral(object value)
        {
            Response.Write(value);
        }
    
         /// <summary>
        /// Razor Parser implements this method
        /// </summary>
        public virtual void Execute() {}
    
    
        public virtual void Dispose()
        {        
            if (Response != null)
            {
                Response.Dispose();
                Response = null;
            }            
        }
    }

    Razor fills in the Execute method when it generates its subclass and uses the Write() method to output content. As you can see I use a RazorResponse() class here to generate output. This isn’t necessary really, as you could use a StringBuilder or StringWriter() directly, but I prefer using Response object so I can extend the Response behavior as needed.

    The RazorResponse class is also very simple and merely acts as a wrapper around a TextWriter:

    public class RazorResponse : IDisposable        
    {
    
        /// <summary>
        /// Internal text writer - default to StringWriter()
        /// </summary>
        public TextWriter Writer = new StringWriter();
    
    
        public virtual void Write(object value)
        {
            Writer.Write(value);
        }
    
        public virtual void WriteLine(object value)
        {
            Write(value);
            Write("\r\n");
        }
    
        public virtual void WriteFormat(string format, params object[] args)
        {
            Write(string.Format(format, args));        
        }
    
        public override string ToString()
        {
            return Writer.ToString();
        }
    
        public virtual void Dispose()
        {
            Writer.Close();
        }
    
        public virtual void SetTextWriter(TextWriter writer)
        {
            // Close original writer
            if (Writer != null)
                Writer.Close();
                        
            Writer = writer;
        }
    }

    The Rendering Methods of RazorEngine

    At this point I’ve talked about the assembly generation logic and the template implementation itself. What’s left is that once you’ve generated the assembly is to execute it. The code to do this is handled in the various RenderXXX methods of the RazorEngine class. Let’s look at the lowest level one of these which is RenderTemplateFromAssembly() and a couple of internal support methods that handle instantiating and invoking of the generated template method:

    public string RenderTemplateFromAssembly(
        string assemblyId,
        string generatedNamespace,
        string generatedClass,
        object context,
        TextWriter outputWriter)
    {
        this.SetError();
    
        Assembly generatedAssembly = AssemblyCache[assemblyId];
        if (generatedAssembly == null)
        {
            this.SetError(Resources.PreviouslyCompiledAssemblyNotFound);
            return null;
        }
    
        string className = generatedNamespace + "." + generatedClass;
    
        Type type;
        try
        {
            type = generatedAssembly.GetType(className);
        }   
        catch (Exception ex)
        {
            this.SetError(Resources.UnableToCreateType + className + ": " + ex.Message);
            return null;
        }
    
        // Start with empty non-error response (if we use a writer)
        string result = string.Empty;
    
        using(TBaseTemplateType instance = InstantiateTemplateClass(type))
        {
            if (instance == null)
                return null;
       
            if (outputWriter != null)
                instance.Response.SetTextWriter(outputWriter);
    
            if (!InvokeTemplateInstance(instance, context))
                return null;
    
            // Capture string output if implemented and return
            // otherwise null is returned
            if (outputWriter == null)
                result = instance.Response.ToString();
        }
        
    
        return result;
    }protected virtual TBaseTemplateType InstantiateTemplateClass(Type type)
    {
        TBaseTemplateType instance = Activator.CreateInstance(type) as TBaseTemplateType;
    
        if (instance == null)
        {
            SetError(Resources.CouldnTActivateTypeInstance + type.FullName);
            return null;
        }
    
        instance.Engine = this;
    
        // If a HostContainer was set pass that to the template too
        instance.HostContainer = this.HostContainer;
        
        return instance;
    }
    
    /// <summary>
    /// Internally executes an instance of the template,
    /// captures errors on execution and returns true or false
    /// </summary>
    /// <param name="instance">An instance of the generated template</param>
    /// <returns>true or false - check ErrorMessage for errors</returns>
    protected virtual bool InvokeTemplateInstance(TBaseTemplateType instance, object context)
    {
        try
        {
            instance.Context = context;
            instance.Execute();
        }
        catch (Exception ex)
        {
            this.SetError(Resources.TemplateExecutionError + ex.Message);
            return false;
        }
        finally
        {
            // Must make sure Response is closed
            instance.Response.Dispose();
        }
        return true;
    }

    The RenderTemplateFromAssembly method basically requires the namespace and class to instantate and creates an instance of the class using InstantiateTemplateClass(). It then invokes the method with InvokeTemplateInstance(). These two methods are broken out because they are re-used by various other rendering methods and also to allow subclassing and providing additional configuration tasks to set properties and pass values to templates at execution time. In the default mode instantiation sets the Engine and HostContainer (discussed later) so the template can call back into the template engine, and the context is set when the template method is invoked.

    The various RenderXXX methods use similar code although they create the assemblies first. If you’re after potentially cashing assemblies the method is the one to call and that’s exactly what the two HostContainer classes do. More on that in a minute, but before we get into HostContainers let’s talk about AppDomain hosting and the like.

    Running Templates in their own AppDomain

    With the RazorEngine class above, when a template is parsed into an assembly and executed the assembly is created (in memory or on disk – you can configure that) and cached in the current AppDomain. In .NET once an assembly has been loaded it can never be unloaded so if you’re loading lots of templates and at some time you want to release them there’s no way to do so. If however you load the assemblies in a separate AppDomain that new AppDomain can be unloaded and the assemblies loaded in it with it.

    In order to host the templates in a separate AppDomain the easiest thing to do is to run the entire RazorEngine in a separate AppDomain. Then all interaction occurs in the other AppDomain and no further changes have to be made. To facilitate this there is a RazorEngineFactory which has methods that can instantiate the RazorHost in a separate AppDomain as well as in the local AppDomain. The host creates the remote instance and then hangs on to it to keep it alive as well as providing methods to shut down the AppDomain and reload the engine.

    Sounds complicated but cross-AppDomain invocation is actually fairly easy to implement. Here’s some of the relevant code from the RazorEngineFactory class. Like the RazorEngine this class is generic and requires a template base type in the generic class name:

        public class RazorEngineFactory<TBaseTemplateType>
            where TBaseTemplateType : RazorTemplateBase
    


    Here are the key methods of interest:

    /// <summary>
    /// Creates an instance of the RazorHost in a new AppDomain. This 
    /// version creates a static singleton that that is cached and you
    /// can call UnloadRazorHostInAppDomain to unload it.
    /// </summary>
    /// <returns></returns>
    public static RazorEngine<TBaseTemplateType> CreateRazorHostInAppDomain()
    {
        if (Current == null)            
            Current = new RazorEngineFactory<TBaseTemplateType>();
    
        return Current.GetRazorHostInAppDomain();
    }
    
    public static void UnloadRazorHostInAppDomain()
    {
        if (Current != null)
            Current.UnloadHost();
        Current = null;
    }
    
    /// <summary>
    /// Instance method that creates a RazorHost in a new AppDomain.
    /// This method requires that you keep the Factory around in
    /// order to keep the AppDomain alive and be able to unload it.
    /// </summary>
    /// <returns></returns>
    public RazorEngine<TBaseTemplateType> GetRazorHostInAppDomain()
    {                        
        LocalAppDomain = CreateAppDomain(null);
        if (LocalAppDomain  == null)
            return null;
    
        /// Create the instance inside of the new AppDomain
        /// Note: remote domain uses local EXE's AppBasePath!!!
        RazorEngine<TBaseTemplateType> host  = null;
    
        try
        {
            Assembly ass = Assembly.GetExecutingAssembly();
    
            string AssemblyPath = ass.Location;
    
            host = (RazorEngine<TBaseTemplateType>) LocalAppDomain.CreateInstanceFrom(AssemblyPath,
                                                   typeof(RazorEngine<TBaseTemplateType>).FullName).Unwrap();
        }
        catch (Exception ex)
        {
            ErrorMessage = ex.Message;
            return null;
        }
    
        return host;
    }
    
    /// <summary>
    /// Internally creates a new AppDomain in which Razor templates can
    /// be run.
    /// </summary>
    /// <param name="appDomainName"></param>
    /// <returns></returns>
    private AppDomain CreateAppDomain(string appDomainName)
    {
        if (appDomainName == null)
            appDomainName = "RazorHost_" + Guid.NewGuid().ToString("n");
    
        AppDomainSetup setup = new AppDomainSetup();
    
        // *** Point at current directory
        setup.ApplicationBase = AppDomain.CurrentDomain.BaseDirectory;
    
        AppDomain localDomain = AppDomain.CreateDomain(appDomainName, null, setup);
    
        return localDomain;
    }
    
    /// <summary>
    /// Allow unloading of the created AppDomain to release resources
    /// All internal resources in the AppDomain are released including
    /// in memory compiled Razor assemblies.
    /// </summary>
    public void UnloadHost()
    {
        if (this.LocalAppDomain != null)
        {
            AppDomain.Unload(this.LocalAppDomain);
            this.LocalAppDomain = null;
        }
    }

    The static CreateRazorHostInAppDomain() is the key method that startup code usually calls. It uses a Current singleton instance to an instance of itself that is created cross AppDomain and is kept alive because it’s static. GetRazorHostInAppDomain actually creates a cross-AppDomain instance which first creates a new AppDomain and then loads the RazorEngine into it. The remote Proxy instance is returned as a result to the method and can be used the same as a local instance.

    The code to run with a remote AppDomain is simple:

    private RazorEngine<RazorTemplateBase> CreateHost()
         {
             if (this.Host != null)
                 return this.Host;
    
             // Use Static Methods - no error message if host doesn't load
             this.Host = RazorEngineFactory<RazorTemplateBase>.CreateRazorHostInAppDomain();
             if (this.Host == null)
             {
                 MessageBox.Show("Unable to load Razor Template Host",
                                 "Razor Hosting", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
             }    
    
             return this.Host;
         }

    This code relies on a local reference of the Host which is kept around for the duration of the app (in this case a form reference). To use this you’d simply do:

    this.Host = CreateHost();if (host == null)
                return;
    
    string result = host.RenderTemplate(
                    this.txtSource.Text,
                    new string[] { "System.Windows.Forms.dll", "Westwind.Utilities.dll" },
                    this.CustomContext);
    
    if (result == null)
    {
       MessageBox.Show(host.ErrorMessage, "Template Execution Error", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
       return;
    }
    
    this.txtResult.Text = result;

    Now all templates run in a remote AppDomain and can be unloaded with simple code like this:

    RazorEngineFactory<RazorTemplateBase>.UnloadRazorHostInAppDomain();
    this.Host = null;

    One Step further – Providing a caching ‘Runtime’

    Once we can load templates in a remote AppDomain we can add some additional functionality like assembly caching based on application specific features. One of my typical scenarios is to render templates out of a scripts folder. So all templates live in a folder and they change infrequently. So a Folder based host that can compile these templates once and then only recompile them if something changes would be ideal.

    Enter host containers which are basically wrappers around the RazorEngine<t> and RazorEngineFactory<t>. They provide additional logic for things like file caching based on changes on disk or string hashes for string based template inputs. The folder host also provides for partial rendering logic through a custom template base implementation.

    There’s a base implementation in RazorBaseHostContainer, which provides the basics for hosting a RazorEngine, which includes the ability to start and stop the engine, cache assemblies and add references:

    public abstract class RazorBaseHostContainer<TBaseTemplateType> : MarshalByRefObject
            where TBaseTemplateType : RazorTemplateBase, new()
    {
            
        public RazorBaseHostContainer()
        {
            UseAppDomain = true;
            GeneratedNamespace = "__RazorHost";
        }
    
        /// <summary>
        /// Determines whether the Container hosts Razor
        /// in a separate AppDomain. Seperate AppDomain 
        /// hosting allows unloading and releasing of 
        /// resources.
        /// </summary>
        public bool UseAppDomain { get; set; }
    
        /// <summary>
        /// Base folder location where the AppDomain 
        /// is hosted. By default uses the same folder
        /// as the host application.
        /// 
        /// Determines where binary dependencies are
        /// found for assembly references.
        /// </summary>
        public string BaseBinaryFolder { get; set; }
    
        /// <summary>
        /// List of referenced assemblies as string values.
        /// Must be in GAC or in the current folder of the host app/
        /// base BinaryFolder        
        /// </summary>
        public List<string> ReferencedAssemblies = new List<string>();
    
    
        /// <summary>
        /// Name of the generated namespace for template classes
        /// </summary>
        public string GeneratedNamespace {get; set; }
            
        /// <summary>
        /// Any error messages
        /// </summary>
        public string ErrorMessage { get; set; }
    
        /// <summary>
        /// Cached instance of the Host. Required to keep the
        /// reference to the host alive for multiple uses.
        /// </summary>
        public RazorEngine<TBaseTemplateType> Engine;
    
        /// <summary>
        /// Cached instance of the Host Factory - so we can unload
        /// the host and its associated AppDomain.
        /// </summary>
        protected RazorEngineFactory<TBaseTemplateType> EngineFactory;
    
        /// <summary>
        /// Keep track of each compiled assembly
        /// and when it was compiled.
        /// 
        /// Use a hash of the string to identify string
        /// changes.
        /// </summary>
        protected Dictionary<int, CompiledAssemblyItem> LoadedAssemblies = 
    new Dictionary<int, CompiledAssemblyItem>(); /// <summary> /// Call to start the Host running. Follow by a calls to RenderTemplate to /// render individual templates. Call Stop when done. /// </summary> /// <returns>true or false - check ErrorMessage on false </returns> public virtual bool Start() { if (Engine == null) { if (UseAppDomain) Engine = RazorEngineFactory<TBaseTemplateType>.CreateRazorHostInAppDomain(); else Engine = RazorEngineFactory<TBaseTemplateType>.CreateRazorHost(); Engine.Configuration.CompileToMemory = true; Engine.HostContainer = this; if (Engine == null) { this.ErrorMessage = EngineFactory.ErrorMessage; return false; } } return true; } /// <summary> /// Stops the Host and releases the host AppDomain and cached /// assemblies. /// </summary> /// <returns>true or false</returns> public bool Stop() { this.LoadedAssemblies.Clear(); RazorEngineFactory<RazorTemplateBase>.UnloadRazorHostInAppDomain(); this.Engine = null; return true; }
    …
    }

    This base class provides most of the mechanics to host the runtime, but no application specific implementation for rendering. There are rendering functions but they just call the engine directly and provide no caching – there’s no context to decide how to cache and reuse templates. The key methods are Start and Stop and their main purpose is to start a new AppDomain (optionally) and shut it down when requested.

    The RazorFolderHostContainer – Folder Based Runtime Hosting

    Let’s look at the more application specific RazorFolderHostContainer implementation which is defined like this:

    public class RazorFolderHostContainer : RazorBaseHostContainer<RazorTemplateFolderHost>

    Note that a customized RazorTemplateFolderHost class template is used for this implementation that supports partial rendering in form of a RenderPartial() method that’s available to templates.

    The folder host’s features are:

    • Render templates based on a Template Base Path (a ‘virtual’ if you will)
    • Cache compiled assemblies based on the relative path and file time stamp
    • File changes on templates cause templates to be recompiled into new assemblies
    • Support for partial rendering using base folder relative pathing

    As shown in the startup examples earlier host containers require some startup code with a HostContainer tied to a persistent property (like a Form property):

    // The base path for templates - templates are rendered with relative paths
    // based on this path.
    HostContainer.TemplatePath = Path.Combine(Environment.CurrentDirectory, TemplateBaseFolder); 
    
    // Default output rendering disk location
    HostContainer.RenderingOutputFile = Path.Combine(HostContainer.TemplatePath, "__Preview.htm");
    
    // Add any assemblies you want reference in your templates
    HostContainer.ReferencedAssemblies.Add("System.Windows.Forms.dll");
    
    // Start up the host container
    HostContainer.Start();

    Once that’s done, you can render templates with the host container:

    // Pass the template path for full filename seleted with OpenFile Dialog    
    // relativepath is: subdir\file.cshtml or file.cshtml or ..\file.cshtml
    var relativePath = Utilities.GetRelativePath(fileName, HostContainer.TemplatePath); if (!HostContainer.RenderTemplate(relativePath, Context, HostContainer.RenderingOutputFile)) { MessageBox.Show("Error: " + HostContainer.ErrorMessage); return; } webBrowser1.Navigate("file://" + HostContainer.RenderingOutputFile);

    The most critical task of the RazorFolderHostContainer implementation is to retrieve a template from disk, compile and cache it and then deal with deciding whether subsequent requests need to re-compile the template or simply use a cached version. Internally the GetAssemblyFromFileAndCache() handles this task:

    /// <summary>
     /// Internally checks if a cached assembly exists and if it does uses it
     /// else creates and compiles one. Returns an assembly Id to be 
     /// used with the LoadedAssembly list.
     /// </summary>
     /// <param name="relativePath"></param>
     /// <param name="context"></param>
     /// <returns></returns>
     protected virtual CompiledAssemblyItem GetAssemblyFromFileAndCache(string relativePath)
     {
         string fileName = Path.Combine(TemplatePath, relativePath).ToLower();
         int fileNameHash = fileName.GetHashCode();
         if (!File.Exists(fileName))
         {
             this.SetError(Resources.TemplateFileDoesnTExist + fileName);
             return null;
         }
    
         CompiledAssemblyItem item = null;
         this.LoadedAssemblies.TryGetValue(fileNameHash, out item);
    
         string assemblyId = null;
    
         // Check for cached instance
         if (item != null)
         {
             var fileTime = File.GetLastWriteTimeUtc(fileName);
             if (fileTime <= item.CompileTimeUtc)
                 assemblyId = item.AssemblyId;
         }
         else
             item = new CompiledAssemblyItem();
    
         // No cached instance - create assembly and cache
         if (assemblyId == null)
         {
             string safeClassName = GetSafeClassName(fileName);
             StreamReader reader = null;
             try
             {
                 reader = new StreamReader(fileName, true);
             }
             catch (Exception ex)
             {
                 this.SetError(Resources.ErrorReadingTemplateFile + fileName);
                 return null;
             }
    
             assemblyId = Engine.ParseAndCompileTemplate(this.ReferencedAssemblies.ToArray(), reader);
    
             // need to ensure reader is closed
             if (reader != null)
                 reader.Close();
    
             if (assemblyId == null)
             {
                 this.SetError(Engine.ErrorMessage);
                 return null;
             }
    
             item.AssemblyId = assemblyId;
             item.CompileTimeUtc = DateTime.UtcNow;
             item.FileName = fileName;
             item.SafeClassName = safeClassName;
    
             this.LoadedAssemblies[fileNameHash] = item;
         }
    
         return item;
     }


    This code uses a LoadedAssembly dictionary which is comprised of a structure that holds a reference to a compiled assembly, a full filename and file timestamp and an assembly id. LoadedAssemblies (defined on the base class shown earlier) is essentially a cache for compiled assemblies and they are identified by a hash id. In the case of files the hash is a GetHashCode() from the full filename of the template. The template is checked for in the cache and if not found the file stamp is checked. If that’s newer than the cache’s compilation date the template is recompiled otherwise the version in the cache is used. All the core work defers to a RazorEngine<T> instance to ParseAndCompileTemplate().

    The three rendering specific methods then are rather simple implementations with just a few lines of code dealing with parameter and return value parsing:

    /// <summary>
    /// Renders a template to a TextWriter. Useful to write output into a stream or
    /// the Response object. Used for partial rendering.
    /// </summary>
    /// <param name="relativePath">Relative path to the file in the folder structure</param>
    /// <param name="context">Optional context object or null</param>
    /// <param name="writer">The textwriter to write output into</param>
    /// <returns></returns>
    public bool RenderTemplate(string relativePath, object context, TextWriter writer)
    {
    
        // Set configuration data that is to be passed to the template (any object) 
        Engine.TemplatePerRequestConfigurationData = new RazorFolderHostTemplateConfiguration()
        {
            TemplatePath = Path.Combine(this.TemplatePath, relativePath),
            TemplateRelativePath = relativePath,
        };
    
        CompiledAssemblyItem item = GetAssemblyFromFileAndCache(relativePath);
    
        if (item == null)
        {
            writer.Close();
            return false;
        }
    
        try
        {
            // String result will be empty as output will be rendered into the
            // Response object's stream output. However a null result denotes
            // an error 
            string result = Engine.RenderTemplateFromAssembly(item.AssemblyId, context, writer);
    
            if (result == null)
            {
                this.SetError(Engine.ErrorMessage);
                return false;
            }
        }
        catch (Exception ex)
        {
            this.SetError(ex.Message);
            return false;
        }
        finally
        {
            writer.Close();
        }
    
        return true;
    }
    
    /// <summary>
    /// Render a template from a source file on disk to a specified outputfile.
    /// </summary>
    /// <param name="relativePath">Relative path off the template root folder. Format: path/filename.cshtml</param>
    /// <param name="context">Any object that will be available in the template as a dynamic of this.Context</param>
    /// <param name="outputFile">Optional - output file where output is written to. If not specified the 
    /// RenderingOutputFile property is used instead
    /// </param>       
    /// <returns>true if rendering succeeds, false on failure - check ErrorMessage</returns>
    public bool RenderTemplate(string relativePath, object context, string outputFile)
    {
        if (outputFile == null)
            outputFile = RenderingOutputFile;
    
        try
        {
            using (StreamWriter writer = new StreamWriter(outputFile, false,
                                                            Engine.Configuration.OutputEncoding,
                                                            Engine.Configuration.StreamBufferSize))
            {
                return RenderTemplate(relativePath, context, writer);
            }
        }
        catch (Exception ex)
        {
            this.SetError(ex.Message);
            return false;
        }
    
        return true;
    }
    
    
    /// <summary>
    /// Renders a template to string. Useful for RenderTemplate 
    /// </summary>
    /// <param name="relativePath"></param>
    /// <param name="context"></param>
    /// <returns></returns>
    public string RenderTemplateToString(string relativePath, object context)
    {
        string result = string.Empty;
    
        try
        {
            using (StringWriter writer = new StringWriter())
            {
                // String result will be empty as output will be rendered into the
                // Response object's stream output. However a null result denotes
                // an error 
                if (!RenderTemplate(relativePath, context, writer))
                {
                    this.SetError(Engine.ErrorMessage);
                    return null;
                }
    
                result = writer.ToString();
            }
        }
        catch (Exception ex)
        {
            this.SetError(ex.Message);
            return null;
        }
    
        return result;
    }

    The idea is that you can create custom host container implementations that do exactly what you want fairly easily. Take a look at both the RazorFolderHostContainer and RazorStringHostContainer classes for the basic concepts you can use to create custom implementations.

    Notice also that you can set the engine’s PerRequestConfigurationData() from the host container:

    // Set configuration data that is to be passed to the template (any object) 
    Engine.TemplatePerRequestConfigurationData = new RazorFolderHostTemplateConfiguration()
    {
        TemplatePath = Path.Combine(this.TemplatePath, relativePath),
        TemplateRelativePath = relativePath,
    };

    which when set to a non-null value is passed to the Template’s InitializeTemplate() method. This method receives an object parameter which you can cast as needed:

    public override void InitializeTemplate(object configurationData)
    {
        // Pick up configuration data and stuff into Request object
        RazorFolderHostTemplateConfiguration config = configurationData as RazorFolderHostTemplateConfiguration;
    
        this.Request.TemplatePath = config.TemplatePath;
        this.Request.TemplateRelativePath = config.TemplateRelativePath;
    }

    With this data you can then configure any custom properties or objects on your main template class. It’s an easy way to pass data from the HostContainer all the way down into the template. The type you use is of type object so you have to cast it yourself, and it must be serializable since it will likely run in a separate AppDomain.

    This might seem like an ugly way to pass data around – normally I’d use an event delegate to call back from the engine to the host, but since this is running over AppDomain boundaries events get really tricky and passing a template instance back up into the host over AppDomain boundaries doesn’t work due to serialization issues. So it’s easier to pass the data from the host down into the template using this rather clumsy approach of set and forward. It’s ugly, but it’s something that can be hidden in the host container implementation as I’ve done here. It’s also not something you have to do in every implementation so this is kind of an edge case, but I know I’ll need to pass a bunch of data in some of my applications and this will be the easiest way to do so.

    Summing Up

    Hosting the Razor runtime is something I got jazzed up about quite a bit because I have an immediate need for this type of templating/merging/scripting capability in an application I’m working on. I’ve also been using templating in many apps and it’s always been a pain to deal with. The Razor engine makes this whole experience a lot cleaner and more light weight and with these wrappers I can now plug .NET based templating into my code literally with a few lines of code. That’s something to cheer about… I hope some of you will find this useful as well…

    Resources

    The examples and code require that you download the Razor runtimes. Projects are for Visual Studio 2010 running on .NET 4.0

    Posted in Razor  ASP.NET  .NET  

    The Voices of Reason


     

    Fabio Franzini
    December 28, 2010

    # re: Hosting the Razor Engine for Templating in Non-Web Applications

    Nice article Rick! I use it to for my project! Again, congratulations on the excellent idea!

    Dennis Gorelik
    December 28, 2010

    # re: Hosting the Razor Engine for Templating in Non-Web Applications

    Rick,

    It looks like monumental work.
    I think the amount code that is required in order to kick off Razor outside of MVC is a bit too much.
    In order to be useful in custom apps the calling call should look something like this:
    string template = "My @razor @template";
    string result = Razor.Render(template, "razor", "template"[, ... other parameters here ...]);

    Thanks for trying it for us!

    Rick Strahl
    December 28, 2010

    # re: Hosting the Razor Engine for Templating in Non-Web Applications

    @Dennis with the wrapper I have it IS about that much code. It's about 3 lines of code.

    The problem is that if you do it as a one liner you do not get the benefits of a hosted environment - caching, AppDomain hosting, additional assembly references etc. and so it takes a few extra lines (with the wrapper code).

    A core engine has to have more flexibility and comparatively the native Razor engine is VERY easy to set up. Compared to ASP.NET runtime hosting, or VBA hosting this is practical trivial and it supports most of the options that one needs to build an effective scripting environment...

    Donnie Hale
    December 28, 2010

    # re: Hosting the Razor Engine for Templating in Non-Web Applications

    I think this is the longest article I've ever seen you post (in a single part, anyway). Are there advantages to using Razor over T4 (now that it's a first-class citizen within VS and for standalone applications)?

    Rick Strahl
    December 28, 2010

    # re: Hosting the Razor Engine for Templating in Non-Web Applications

    @Donnie - T4 is specific to Visual Studio so you need Visual Studio to use it which makes it useless for standalone applications. Also I've never been a fan of T4 since there isn't any decent editor support in VS even for it (you need third party tools to do that). Bottom line is if you want to generated code (or whatever) from within visual studio T4 is the easiest choice as the the environment will automatically pick up templates and convert them to output files. For external apps Razor is what you'd want.

    Re: Long Article - yes, in the past I published these as articles, but it's just become easier to publish articles right here in the Blog rather than in a separate location. Easier to edit and maintain right here and more visible to the world on top of it. :-)

    Travis
    December 28, 2010

    # re: Hosting the Razor Engine for Templating in Non-Web Applications

    Great article - thanks for sharing. I haven't needed custom templating in a many, many years, and way back then I was using StringBuilders. Can't wait to find an excuse to try this out :) P.S. Time to upgrade from svn to hg :)

    Paul Cox
    December 29, 2010

    # re: Hosting the Razor Engine for Templating in Non-Web Applications

    Fantastic blog post, could almost be a chapter of a book!

    I've been using Spark for email templating for a while but have wanted to switch to Razor to standardise the templating across MVC views and email views.

    After pluging this in the main problem I ran into was that I couldn't get it to work with anonymous types even when not using a separate AppDomain. I'm not sure if it is possible to get this to work with a remote AppDomain but I've seen it working locally with the Razor templating engine on Codeplex.

    Also I had a couple of questions/requests.

    1. Is the engine thread-safe? I have a singleton RazorFolderHostContainer supplied by main IoC container that is called from my server app running MassTransit. As each message handler runs in a separate thread I could be accessing the engine at the same time across threads to generate 2 separate templates.

    2. Are you planning to add support for Layout pages? Creating an email is pretty much the same as creating a web page so most of the functionality from the RazorViewEngine would be useful.

    Oak Chantosa
    December 29, 2010

    # re: Hosting the Razor Engine for Templating in Non-Web Applications

    Your thoughts on any sort of integration with LINQPad ? http://linqpad.net/

    Rick Strahl
    December 29, 2010

    # re: Hosting the Razor Engine for Templating in Non-Web Applications

    @Paul - No the engine isn't thread safe at this point. I had no intention for this to work in a server environment since you can use the Razor engine natively with ASP.NET MVC. It's something to put on the list for the future possibly. Currently the issue is that compilation is relatively slow and the assembly caching will cause mutli-thread access issues. It should be perfectly possible however to run Razor multi-threaded with the right locking in place.

    Haven't looked into layout pages yet - not quite sure how to implement this inside out partial rendering - I think that would require pre-parsing of the page to get the 'master' then rendering that and rendering the current page as a partial. Again something to think about for the furture. For now the FolderHostContainer supports partial rendering to provide for at least some minimal composition functionality.

    Rick Strahl
    December 29, 2010

    # re: Hosting the Razor Engine for Templating in Non-Web Applications

    @Oak - what kind of integration are you thinking of? LinqPad already has good execution options and the .Dump() command works well for output of values. I'm not sure what a scripting engine would add? Using a script instead of a codeblock?

    0xfeeddeadbeef
    December 30, 2010

    # re: Hosting the Razor Engine for Templating in Non-Web Applications

    There is also much less versatile implementation of this idea: http://razorengine.codeplex.com/

    Duane Craw
    December 30, 2010

    # re: Hosting the Razor Engine for Templating in Non-Web Applications

    Great stuff! I was playing around with this, and found a way to get intellisense on the Context object.

    Add:
    @using mynamespase
    @{MyObject Model = Context}
       ....content... @Model.MyField ...content
    


    This makes editing the .cshtml template much easier!

    Takashi,Takehara
    January 01, 2011

    # re: Hosting the Razor Engine for Templating in Non-Web Applications

    Great atricle!

    I wrote sample program RazorHosting, but I can not call System.Web.Mvc.HtmlHelper.
    New base class have HtmlHelper Html property. It coding referring to Razor Templating Engine.
    Why I has MethodAccessException.
    System.ComponentModel.DataAnnotations APTCA assembly...

    something a good idea?

    Steve
    January 01, 2011

    # re: Hosting the Razor Engine for Templating in Non-Web Applications

    Great post Rick. I've used NVelocity to do this work for several years now. It will be interesting to try this out using Razor. Thanks to your write-up it might be something less painful to try on the first attempt :)

    Thanks Again! Happy New Year!

    Rick Strahl
    January 01, 2011

    # re: Hosting the Razor Engine for Templating in Non-Web Applications

    @Takashi - MVC Helpers aren't available by default. You're not running MVC when you're hosting Razor yourself so you don't get all the same assembly includes nor the same feature set you get when using the hosting engine on its own. I think the MVC helpers have all sorts of dependencies that will not make them easily useable in a self hosted environment.

    However, ASP.NET Web Pages (which is the package that Razor comes in) includes System.Web.Helpers.dll that provides some similar functionality and that isn't tied closely to the MVC engine but to the standalone Razor sytnax use in WebMatrix.

    Eber Irigoyen
    January 02, 2011

    # re: Hosting the Razor Engine for Templating in Non-Web Applications

    "Razor is an alternative view engine that can be used instead of the ASP.NET Page engine that currently works with ASP.NET WebForms and MVC."
    It *can be used in WebForms?

    Rick Strahl
    January 02, 2011

    # re: Hosting the Razor Engine for Templating in Non-Web Applications

    @Eber - yes you can you can use Razor files inside of an ASP.NET WebForms site. Simply add the .csHtml or .vbHtml files into the project and then call the new files without the .csHtml extension (they are auto-routed so /mysite/Default.csHtml becomes /mysite/Default). This is assuming you installed MVC 3 or WebMatrix to get Razor and the VS tooling onto the machine in the first place.

    What you can't do is mix and match Razor and ASP.NET WebForms syntax within a single page (other than embedding content from an iFrame or AJAX load operations).

    Eber Irigoyen
    January 04, 2011

    # re: Hosting the Razor Engine for Templating in Non-Web Applications

    oh ok, that's what I thought, just wanted to make sure you didn't have another one under your sleeve

    Anders Lybecker
    January 04, 2011

    # re: Hosting the Razor Engine for Templating in Non-Web Applications

    Great stuff.

    I’m trying to reference the current assembly, the one that uses the RazorEngine, without success. I’m have implemented helper template methods, sort of like regular MVC HtmlHelpers, but they all reside inside the same assembly.

    I prefer not using the CustomContext, as it for data, but it’s my current workaround. I’m not able to user partial render, as the current implementation is only file based. My template views are embedded resources.

    Any suggestions?

    Rick Strahl
    January 04, 2011

    # re: Hosting the Razor Engine for Templating in Non-Web Applications

    @Anders - How are you trying to access the helper assembly? Make sure you add the ReferencedAssemblies in startup as well as ReferencedNamespaces (in the latest build in SVN).

    You can also create custom templates now with RazorFolderHostContainer<T> and then override InitializeTemplate(context,config) to pass in the context and break it out into strongly typed objects.

    If you have problems of things that are failing more specific info will help. You can post that here (which is a better forum for interactive discussions):
    http://www.west-wind.com/wwThreads/default.asp?Forum=West+Wind+Web+Toolkit+for+ASP.NET

    # TWC9: WebMatrix, MVC, Orchard, NuGet Gallery

    Don’t have Silverlight? Download it here ! Or, download the video as a high quality WMV , medium quality

    David Klebanoff
    March 11, 2011

    # re: Hosting the Razor Engine for Templating in Non-Web Applications

    Hello Rick,
    Great article and software. Exactly what I'm loolking for. But one thing, you have not clarified the license for RazorHosting. What is it?

    sri
    June 17, 2011

    # re: Hosting the Razor Engine for Templating in Non-Web Applications

    Hello Rick,

    Very dense and useful article. I am trying to achieve the same and your article helped me a lot.
    However I am facing am issue when I refer an assembly "MyAssembly.dll".

    I have created a Class Library project "RazorBuilder" which exposes method to return the html output of the razor.

    I have added the reference assembly as below
    result = host.RenderTemplate(reader,
    new string[] { "System.Web.dll","MyAssembly.dll" },
    Context);

    This works fine when I run the test methods in the Test Project by referencing the "RazorBuilder".

    The same does not work when i move the "RazorBuilder" into the Windows Service . Gives the error "Error: Metadata file 'MyAssembly.dll' could not be found"

    Can you help me out on this.
    Thanks in advance.
    But When i move the same to Windows Service , it fails with an error:
    Line: 0\t Col: 0\t Error: Metadata file 'MyAssembly.dll' could not be found .

    Could you please help me out on this.
    Thanks in advance.

    elmar
    June 27, 2011

    # re: Hosting the Razor Engine for Templating in Non-Web Applications

    Dear Rick,

    This contribution is great. Thanks for that. Question though: the RenderTemplate keeps returning null and the errormessage says it can't find a referenced assembly. When I supply the full path to the assembly, the template engine still complains about unable to find assembly. But it is there.

    Any ideas?

    Thanks

    Elmar

    Rick Strahl
    June 27, 2011

    # re: Hosting the Razor Engine for Templating in Non-Web Applications

    @Elmar - assemblies should be in the host application's assembly path (ie. startup folder or privateBin folder). If necessary you can adjust the bin path with startup/config file settings to accomodate additional folders.

    Additionally if you use in process hosting you should be able to just load the assembly into the current process and it should just work since in that case the AppDomain and all assembly loads are shared.

    Pranas
    June 28, 2011

    # re: Hosting the Razor Engine for Templating in Non-Web Applications

    Great article, Rick.
    Is there a possibility to debug cshtml file?

    Rick Strahl
    June 28, 2011

    # re: Hosting the Razor Engine for Templating in Non-Web Applications

    @Pranas - yes. If you have Visual Studio 2010 with the Razor extensions installed just set a breakpoint in a code block anywhere and the debugger will pop.

    Pranas
    June 29, 2011

    # re: Hosting the Razor Engine for Templating in Non-Web Applications

    What is "Razor Extensions"? Is it part of ASP.NET MVC3 install?
    I can debug cshtml files in ASP.NET MVC application, but I can't debug cshtml files in your sample application.
    I tried to change your RazorEngine to generate .pdb file together with .dll from .cshtml file. And when this generated assembly is loaded to AppDomain, I can see that symbol file is also loaded. The generated .cs file from .cshtml is also in temp directory, but I don't understand how debugger finds its .cs and .cshtml file from .pdb.
    Do you know anything about this?
    Have you tried to use debugger in your sample application to debug .cshtml file?

    Rick Strahl
    June 29, 2011

    # re: Hosting the Razor Engine for Templating in Non-Web Applications

    @Pranas - I think you have to install the Razor Syntax color highlighting for Visual Studio 2010 separately from the Component Gallery to get syntax coloring and editor mapping so you can set a breakpoint in the VS editor. Otherwise you have to set a manual breakpoint with System.Diagnostics.Debugger.Break().

    Scott Holodak
    July 05, 2011

    # re: Hosting the Razor Engine for Templating in Non-Web Applications

    Have you seen David Ebbo's post on pre-compiling Razor views?
    http://blog.davidebbo.com/2011/06/precompile-your-mvc-views-using.html

    It would be very cool if compiled Razor views could live in a project with no web/MVC dependencies and the binary Razor views in that .dll could be leveraged by both MVC apps and standalone applications/services. I could see some very interesting use cases.

    For instance, we have a need to present a very large HTML-based form both online and in a WinForms WebBrowser control. We would like to leverage the same exact templates for both, without having to have .cshtml files editable by the user on the filesystem in the WinForms app.

    Any idea if it would be possible to pre-populate the page/partial page cache for high-volume sites. You could render the templates into the cache and eliminate the user waiting on the cache miss. This could be useful for sites that perform overnight calculations or even posts to high-volume forums/threads/blog comments.

    Rick Strahl
    July 05, 2011

    # re: Hosting the Razor Engine for Templating in Non-Web Applications

    @Scott - the code has no MVC/Web dependencies now. Other than VS editing support the razor libraries are self contained and fully portable without any pre-installed libraries.

    Pre-compilation is not that important IMHO - the whole point of a scripting engine should be that it is dynamic. The code I provide can actually write out the compiled assemblies and be reused although I think that's not all that useful. Compilation is pretty fast (unlike the full ASP.NET runtime which was super resource extensive) and pages are cached afterwards if you use one of the host containers I talk about in the article.

    Scott Holodak
    July 05, 2011

    # re: Hosting the Razor Engine for Templating in Non-Web Applications

    The benefit of the compiled views here would be that the MVC app and the WinForms/WPF app would be referring to the same exact views in the win/web version. So if we make changes to the views (or the underlying models), the MVC app and the WinForms/WPF app would both inherit the changes and would render the same HTML.

    I'll try and mash these two solutions together and post back if I have any luck.

    MvcApp.sln
    - Shared\Data.csproj
    - Shared\Models.csproj
    - Shared\RazorViews.csproj
    - MvcApp.csproj

    WinFormsOrWpfApp.sln
    - Shared\Data.csproj
    - Shared\Models.csproj
    - Shared\RazorViews.csproj
    - WinFormsOrWpfApp.csproj

    Harry
    July 05, 2011

    # re: Hosting the Razor Engine for Templating in Non-Web Applications

    I am using the RenderPartial in the razor view and getting an error :Line: 399\t Col: 28\t Error: The name 'RenderPartial' does not exist in the current context.

    What am I missing here?

    Rick Strahl
    July 06, 2011

    # re: Hosting the Razor Engine for Templating in Non-Web Applications

    @Harry - are you using the FolderHostContainer? RenderPartial() is not built into Razor natively so it requires a custom template and the mechanism to look up a partial path. The implementation for this is in the FolderHostContainer and the associated custom template.

    Paul Perrick
    December 10, 2011

    # re: Hosting the Razor Engine for Templating in Non-Web Applications

    First of all, thank you for writing this library! It is exactly what I was looking for and I don't have to write anything similar myself.

    Please please please host it on a site like github so a forum is included. From what I can see in the above comments, people are using the library (or want to use it) and if there are questions, it is difficult to organize questions/answers in the comments of a blog.

    Thank You,

    Paul

    Rick Strahl
    December 10, 2011

    # re: Hosting the Razor Engine for Templating in Non-Web Applications

    @Paul - you can post questions and comments for forum discussion here:

    http://www.west-wind.com/wwthreads/default.asp?forum=West%20Wind%20.NET%20Tools%20and%20Demos

    Andrew
    February 13, 2012

    # re: Hosting the Razor Engine for Templating in Non-Web Applications

    Hi Rick, excellent stuff, i'm using a slightly modified version as an email templating engine and it works really well. Ironically, the email templating engine is used in a domain dll which is referenced by an MVC app ;)
    And that is also the source of a problem: when i call Start() on a RazorStringHostContainer, and the RazorEngineFactory tries to CreateRazorHostInAppDomain(), which in turn calls GetRazorHostInAppDomain() i get an error on this line:

    host = (RazorEngine<TBaseTemplateType>) LocalAppDomain.CreateInstanceFrom(AssemblyPath, typeof(RazorEngine<TBaseTemplateType>).FullName).Unwrap();

    the error i get is "Could not load the file or assembly....The system cannot find the file specified"

    Now i suspect this has something to do with ASP.Net's permissions, but i'm not sure how to solve it. Any ideas?

    Greg Brant
    February 20, 2012

    # re: Hosting the Razor Engine for Templating in Non-Web Applications

    Rick,

    I'm getting an error when calling RenderTemplate();

    The error is "Couldn't activate type instance: _RazorHost._40a86a00_8583_4bae_8fda_9a82b8866f96"

    My call site looks like this

    var engine = new RazorEngine<LineTemplate>();
    var renderOutput = engine.RenderTemplate(template, new string[] { "Transformer.exe" }, item);

    Transformer.exe is my console application when LineTemplate is declared.

    Aviad Pineles
    March 17, 2012

    # re: Hosting the Razor Engine for Templating in Non-Web Applications

    Hi, there's a missing feature when using strongly typed models, the Model property doesn't get automatically assigned with the context object.

    I've created a small patch to fix it, here it is:

    Index: RazorEngine.cs
    ===================================================================
    --- RazorEngine.cs    (revision 62)
    +++ RazorEngine.cs    (working copy)
    @@ -554,6 +554,15 @@
             {
                 try
                 {
    +                var templateType = instance.GetType();
    +                var templateModelProperty = templateType.GetProperty("Model", BindingFlags.Instance | BindingFlags.Public);
    +                if (templateModelProperty != null)
    +                {
    +                    var contextModelType = context.GetType();
    +                    var templateModelType = templateModelProperty.PropertyType;
    +                    if (templateModelType.IsAssignableFrom(contextModelType))
    +                        templateModelProperty.SetValue(instance, context, null);
    +                }
                     instance.Context = context;
                     instance.Execute();
                 }
    

    Rick Strahl
    March 18, 2012

    # re: Hosting the Razor Engine for Templating in Non-Web Applications

    Hi Avid,

    Great comment. I modded this using dynamic code with a plain assignment and exception trapping for the missing model property:

    instance.Context = context;
                    
    if (context != null)
    {
        // if there's a model property try to 
        // assign it from context
        try
        {
            dynamic dynInstance = instance;
            dynInstance.Model = context;
        }
        catch {}                    
    }
     
    instance.Execute();
    

    Ed
    May 04, 2012

    # re: Hosting the Razor Engine for Templating in Non-Web Applications

    I missed referencing an assembly and RazorFolderHostContainer (Rev 64) failed at line 311 with the following error when trying to access Engine.ErrorMessage:

    Cannot obtain fields or call methods on the instance of type 'RazorHosting.RazorEngine<RazorHosting.RazorTemplateFolderHost>' because it is a proxy to a remote object.

    assemblyId = Engine.ParseAndCompileTemplate(this.ReferencedAssemblies.ToArray(), reader);
     
    // need to ensure reader is closed
    if (reader != null)
        reader.Close();
     
    if (assemblyId == null)
    {
        this.SetError(Engine.ErrorMessage);
        return null;
    }
    

    James
    May 31, 2012

    # re: Hosting the Razor Engine for Templating in Non-Web Applications

    Hi Rick, Just a quick note to say thanks for the code.
    I too was experiencing the same issue as Richard when running this in a web environment, when trying to use host containers in a new AppDomain returning FileNotFoundException.

    In order to resolve this I modified your code slightly in the CreateDomain function of the RazorFactory class.

    Evidence evidence = AppDomain.CurrentDomain.Evidence;
    setup = AppDomain.CurrentDomain.SetupInformation;

    Now, I don't get any FileNotFoundException and it was just a matter of ensuring my complex object that I use as my context was Serializable, and voila !!! We're in business!

    Hope this helps someone.

    Thanks again, off to Maui this summer so very much looking forward to that

    Cameron Elliot
    August 31, 2012

    # re: Hosting the Razor Engine for Templating in Non-Web Applications

    Thanks very much for this code, it has been very helpful. I think it may need a little HtmlEncoding though:

                    //HttpEncode so that anything written out with reserved characters is escaped
            public virtual void Write(object value)
            {
                Response.Write(HttpUtility.HtmlEncode(value));
            }
     
            //HttpEncode so that anything written out with reserved characters is escaped
            public virtual void WriteString(object value)
            {
                Response.Write(HttpUtility.HtmlEncode(value));
                Response.Write(Environment.NewLine);
            }
     
            //No HttpEncode here as we are writing literal content
            public virtual void WriteLiteral(object value)
            {
                Response.Write(value);
            }
    

    Frank Schwengsbier
    October 29, 2012

    # re: Hosting the Razor Engine for Templating in Non-Web Applications

    Hi Rick,

    great work! Do I understand it right? This can be a solution to run custom c# code on-the-fly? Like the:
    - someone edits the x.prg during the runtime of a FoxPro App
    - compile x.prg
    - do x
    commands in FoxPro?

    Regards
    Frank

    Rick Strahl
    October 30, 2012

    # re: Hosting the Razor Engine for Templating in Non-Web Applications

    @Frank - sort of. It's more of a templating mechanism that can also run code rather than a 'scripting' engine. But in theory you can create a template that only runs code. Using the ResultData property of the template and the engine properties in my implementations you can even pass data results back.

    Martin
    April 12, 2013

    # re: Hosting the Razor Engine for Templating in Non-Web Applications

    Thanks, Very useful.

    I had an issue where stopping the Host wasn't unloading the AppDomain successfully though.

    Changing a line in the RazorBaseHostContainer Stop() method to
     RazorEngineFactory<TBaseTemplateType>.UnloadRazorHostInAppDomain();
    Seems to have fixed it.

    BTW: Is there any technical reason for not allowing Exceptions to bubble up or is this just a design decision?

    Alexander Gurevich
    October 30, 2013

    # re: Hosting the Razor Engine for Templating in Non-Web Applications

    Hi,
    Great post!. Thank you very much.
    I am trying to use System.Web.HtmlString. For example:

    @{var expression = new System.Web.HtmlString("<b>Hello</b>");}
    <p>
    @expression
    </p>

    I am getting error:
    Line 90 Col 36: The type or namespace name 'HtmlString' does not exist in the namespace 'System.Web' (are you missing an assembly reference?)
    System.Web is referenced and I see in generated file namespace
    ========================
    namespace @__RazorHost {
    using System;
    using System.Text;
    using System.Collections.Generic;
    using System.Linq;
    using System.IO;
    using System.Web;
    ========================

    Any idea what am I missing?

    Thank you.

    Rick Strahl
    October 30, 2013

    # re: Hosting the Razor Engine for Templating in Non-Web Applications

    @Alexander - are you sure you added a REFERENCE to System.Web?

    Alexander Gurevich
    October 31, 2013

    # re: Hosting the Razor Engine for Templating in Non-Web Applications

    @Rick Strahl
    Yes, I confirmed it. System.Web is in references of my project.
    The weird part is the error says "'HtmlString' does not exist in the namespace 'System.Web'".
    I use full name that includes namespace...

    Is there another way to use html string in c#?

    Rick Strahl
    October 31, 2013

    # re: Hosting the Razor Engine for Templating in Non-Web Applications

    Maybe you're ending up loading the wrong version? You're using the fully qualified assembly name to specify the System.Web instance?

    Alexander Gurevich
    October 31, 2013

    # re: Hosting the Razor Engine for Templating in Non-Web Applications

    @Rick Strahl
    I do not understand your question.

    The project is referenced. System.Web properties:
    Version: 4.0.0.0
    Runtime Version: v4.0.30319

    In Razorengine constructor I added
    ReferencedNamespaces.Add("System.Web");
    

    The error shows me that reference is there
    namespace @__RazorHost {
        using System;
        using System.Text;
        using System.Collections.Generic;
        using System.Linq;
        using System.IO;
        using System.Web;
    


    Where I should have used fully qualified assembly name to specify the System.Web?

    Thank you.

    Rick Strahl
    October 31, 2013

    # re: Hosting the Razor Engine for Templating in Non-Web Applications

    You need to add the assembly in addition to the namespace and that's regardless of whether the assembly is being referenced in your host application. The Razor instance (especially if you are using the separate AppDomain option) requires its own set of assembly references. So make sure you have

    host.AddAssembly("System.Web.dll")
    

    Martin Kral
    January 17, 2014

    # re: Hosting the Razor Engine for Templating in Non-Web Applications

    Hi.
    I using this solution for render some special HTML document without MVC.
    It's very useful !
    But I have problem, if i try render @for (int i = 0; i < Context.Competitors.Count(); i++)
    {
    ....
    where is Competitors some List<Collection>.
    I get this error:
    Non-invocable member 'System.Collections.Generic.List<EventCompetitor>.Count' cannot be used like a method.

    If i have some variable in the Context, where i write Count directly, it's proccessed without error. Where is the problem?
    Thank you.

    Hugo Logmans
    May 16, 2014

    # re: Hosting the Razor Engine for Templating in Non-Web Applications

    Hi Rick,

    first: thanks for your great code and explanation.

    But I run into a issue: if I compile using "CompileToMemory = true", the following situation does not work. If I set it to false, it all works fine.

    Situation: the calling application resides in a "program files" subfolder (vfp9.exe). It calls an in process ActiveX dll (which resides in my dev folder). Dependent assemblies that are not found are redirected to the dev folder. In this case, the generated assembly (which uses the assembly that generates it), cannot load the calling assembly as a dependency. The failure is when compiling the template (that succeeds, but internally a loaderror occurs that seems to be suppressed). I tried some things to provide paths to dependent assemblies, but nothing seems to work when the compiletomemory feature is active...

    Maybe you have any clue?

    Rick Strahl
    May 16, 2014

    # re: Hosting the Razor Engine for Templating in Non-Web Applications

    @Hugo - you probably have a missing assembly somewhere. All dependencies must be resolvable through the bin path. Generally in memory should work with any assembly the top level .NET AppDomain can access (ie. whatever you loaded from FoxPro).

    Iaacov
    April 12, 2015

    # re: Hosting the Razor Engine for Templating in Non-Web Applications

    Hi Rick,

    First, thanks for this great tool.

    We successfully use the RazorHosting wrapper inside a Windows Service that delivers template-based emails.

    The host container is started in the service OnStart event and stopped in OnStop.
    The template runs in a separate AppDomain.

    The problem arises when the service stays idle for more than 5 minutes. In this case the template's lifetime expires and doesn't compile any new code.

    The RazorBaseHostContainer class has an InitializeLifetimeService method which, according to documentation, appears to exists for this purpose but I couldn't figure out how to use it.

    Can you please explain me how to solve this problem?

    Renato
    April 20, 2015

    # re: Hosting the Razor Engine for Templating in Non-Web Applications

    Hi Rick,

    thank you for the great post. Is there any way you can think of to get "Razor Intellisense" working at runtime in an editor? Something like your WinForm example with integrated Razor Intellisense in the "Template to Render" textbox.

    Sandhya
    April 17, 2018

    # re: Hosting the Razor Engine for Templating in Non-Web Applications

    Hi, Is using this RazorEngine (or similar dlls) still the only way to use a templating engine from a Windows Service or a singleton running in asp.net? Or are there any new approaches?


    West Wind  © Rick Strahl, West Wind Technologies, 2005 - 2024