A long time ago, I thought about how to generate emails from a template and I reached out to a few developer friends to kick around several ideas.  We talked about using excel merges, XSLT, regex, and writing a custom processing engine.  When they mentioned an engine I asked if anyone knew about spark.  Spark was a new View Engine that could be plugged into MVC to replace the default Razor View Engine; my theory was if you can swap out the entire view engine maybe you could run it outside of the context of a MVC application in a console application.  There have been a few articles about do that over time, but I never looked into it.

Fast forward to 2017, and the topic of generating emails from a template came up again and here we are.

You can find the repository for this project here - Razor In a console application.

A couple of notes-

On code reviews-
If you start your project and base files without changes in your first commit, it becomes easier to see what has changed in the later code reviews.

It also helps to see the thought process as the code changes in the commits.  

     public class Person
     {
         public int Id { get; set; }
         public string FirstName { get; set; }
         public string LastName { get; set; }
         public string Title { get; set; }
         public string Salutation { get; set; }

     }

    
public interface IDataProvider
     {
         IEnumerable<Person> GetEveryone();
         Person GetPerson(int id);
     }

With all of that said let’s make razor do something.

All of the following steps we done in a the “Core” dll-

  1. Instead of building a large amount of infrastructure code to run the Razor View Engine outside of an MVC app I’ve opted to use the “RazorEngine” Nuget package which has already done the heavy lifting for me.  Add the package to the project. 
  2. Add a templates folder to the project.
  3. Add a cshtml file to the templates folder and set the copy to output directory to “always copy” and the build action to “content”  I added a “welcome.cshtml” file and hardcoded it when I called for the template.…
    Note- In Visual Studio I added a “HTML” file and renamed it as cshtml file because the cshtml file type is not an option for my library project. 
  4. Because we are going to track out own IRazorEngineService I added the IDisposable interface to GenerateText so we can properly dispose of our IRazorEngineService. There is a static instance we could use, but I want to change the default configuration a bit.
  5. In the constructor we setup a custom configuration and create the instance of the “service”
  6. Next add a little code to call the service with the template key to run and compile the template.  This is important, if the cache doesn't already have the compiled template for the model it will throw an exception that there is no template with that key.  I was getting errors calling the “Run” method because the templates had not been compiled yet. 
  7. In order to get the Engine to read our cshtml file set the TemplateManager to the ResolvePathTemplateManager.  It will take an array of strings that point to where you are storing the cshtml templates. 
  8. We also change the caching provider to stop a message about cleaning up certain files.  (I will talk about that a little further down)

using RazorEngine.Configuration;
using RazorEngine.Templating;
using RazorInAConsoleApp.Data;
using System;
using System.Collections.Generic;
using System.IO;

namespace RazorInAConsoleApp.Core
{
     public class GenerateText : IDisposable
     {
         private IRazorEngineService _razor = null;
         private readonly string _templateDirectory = Path.Combine(Environment.CurrentDirectory, "Templates");
         public GenerateText()
         {
             var config = new TemplateServiceConfiguration();
             // .. configure your instance
#if DEBUG
             config.Debug = true;

#else
             config.Debug = false;
#endif
             config.DisableTempFileLocking = !config.Debug;
             config.CachingProvider = new DefaultCachingProvider(t => { });
             config.TemplateManager = new ResolvePathTemplateManager(new[] { _templateDirectory });
             _razor = RazorEngineService.Create(config);
         }
        
         public void GenerateTextForPeople(IEnumerable<Person> people, string directory)
         {
             foreach (Person person in people)
             {
                 string result = _razor.RunCompile("welcome.cshtml", modelType: typeof(Person), model: person);
                 using (StreamWriter writer = File.CreateText(Path.Combine(directory, $"{person.Id}.html")))
                 {
                     writer.Write(result);
                     writer.Flush();
                 }
             }
         }

        #region IDisposable Support
         private bool disposedValue = false; // To detect redundant calls

        protected virtual void Dispose(bool disposing)
         {
             if (!disposedValue)
             {
                 if (disposing)
                 {
                     // TODO: dispose managed state (managed objects).
                     if (_razor != null)
                     {
                         _razor.Dispose();
                     }
                 }

                // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below.
                 // TODO: set large fields to null.

                disposedValue = true;
             }
         }

        // TODO: override a finalizer only if Dispose(bool disposing) above has code to free unmanaged resources.
         // ~GenerateText() {
         //   // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
         //   Dispose(false);
         // }

        // This code added to correctly implement the disposable pattern.
         public void Dispose()
         {
             // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
             Dispose(true);
             // TODO: uncomment the following line if the finalizer is overridden above.
             // GC.SuppressFinalize(this);
         }
         #endregion

    }
}

And now

Running this code generates a html file for each person.  Which was all we were after.

Some notes about the code-

Roslyn

I did try to use the RazorEngine.Roslyn package but I ran into a couple of issue and found that it was still listed as a RC.  I removed it because I wanted to work with “released” code and not be on the bleeding edge.

Parting thoughts

The RazorEngine package made it easy to take advantage of the Razor View Engine.  I think it is a cleaner solution to use “template” text over RegEx and easier than creating a custom engine.


Happy coding