Skip to content

Instantly share code, notes, and snippets.

@ArtemAvramenko
Last active May 3, 2018 22:17
Show Gist options
  • Save ArtemAvramenko/eb13a4bae621e029af5ddb00de75674c to your computer and use it in GitHub Desktop.
Save ArtemAvramenko/eb13a4bae621e029af5ddb00de75674c to your computer and use it in GitHub Desktop.
Use Microsoft.AspNetCore.Mvc.Razor to generate emails
namespace Razor.Mail
{
public class RawString
{
private readonly string _text;
public RawString(string text) => _text = text;
public override string ToString()
{
return _text;
}
}
}
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.AspNetCore.Razor.Language.Extensions;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
namespace Razor.Mail
{
public class TemplateManager
{
public static TemplateManager Default = new TemplateManager(typeof(TemplateManager).Assembly);
public TemplateManager(Assembly resourceAssembly) =>
_resourceAssembly = resourceAssembly;
private const string TemplateNamespace = "Razor.Mail";
private readonly Assembly _resourceAssembly;
private readonly object _compileSync = new object();
private readonly Dictionary<string, Type> _compiledTemplateByName = new Dictionary<string, Type>();
private readonly RazorTemplateEngine _engine = new RazorTemplateEngine(
RazorEngine.Create(builder =>
{
InheritsDirective.Register(builder);
builder.SetNamespace(TemplateNamespace);
builder.SetBaseType(typeof(TemplatePageBase).FullName);
builder.Build();
}),
RazorProject.Create("."));
public string Render(string templateName, object model, Action<TemplatePageBase> init = null)
{
templateName = templateName.Replace('/', '.');
// Get the compiled type
Type templateType;
lock (_compileSync)
{
if (!_compiledTemplateByName.TryGetValue(templateName, out templateType))
{
templateType = Compile(templateName);
_compiledTemplateByName[templateName] = templateType;
}
}
// Create and initialize template
var template = Activator.CreateInstance(templateType) as TemplatePageBase;
template.ModelBase = model;
template.Manager = this;
init?.Invoke(template);
// Run the code.
template.ExecuteAsync().Wait();
// Render the layout recursevly
if (!string.IsNullOrEmpty(template.Layout))
{
return Render(template.Layout, model, t => t.RenderedBody = template.Result);
}
return template.Result;
}
private Type Compile(string templateName)
{
// Read the resource
string templateText;
var resourceName = _resourceAssembly.GetName().Name + "." + templateName;
using (Stream stream = _resourceAssembly.GetManifestResourceStream(resourceName))
using (StreamReader reader = new StreamReader(stream))
{
templateText = reader.ReadToEnd();
}
// Parse and generate C# code
var projectItem = new TemplateProjectItem(templateText);
var csCode = _engine.GenerateCode(projectItem);
Console.WriteLine(csCode.GeneratedCode);
// Parse C# code
var tree = CSharpSyntaxTree.ParseText(csCode.GeneratedCode);
// Define assembly
var compilation = CSharpCompilation.Create(
TemplateNamespace,
new[] { tree },
new[]
{
MetadataReference.CreateFromFile(typeof(object).Assembly.Location), // corlib
MetadataReference.CreateFromFile(Path.Combine(Path.GetDirectoryName(typeof(object).Assembly.Location), "System.Runtime.dll")),
MetadataReference.CreateFromFile(Path.Combine(Path.GetDirectoryName(typeof(object).Assembly.Location), "netstandard.dll")),
MetadataReference.CreateFromFile(typeof(TemplatePageBase).Assembly.Location),
},
new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
// Compile assembly
using (var assemblyStream = new MemoryStream())
{
if (!compilation.Emit(assemblyStream).Success)
{
var error = compilation.Emit(assemblyStream).Diagnostics.FirstOrDefault()?.ToString() ?? "Invalid template";
throw new InvalidOperationException(error);
}
assemblyStream.Seek(0, SeekOrigin.Begin);
// Load type from compiled assembly
var assembly = Assembly.Load(assemblyStream.ToArray());
return assembly.GetType(TemplateNamespace + ".Template");
}
}
}
}
using System.Text;
using System.Threading.Tasks;
using System.Web;
namespace Razor.Mail
{
public abstract class TemplatePageBase
{
private readonly StringBuilder _result = new StringBuilder();
private string _attributeSuffix;
public object ModelBase { get; set; }
public TemplateManager Manager { get; set; }
public string RenderedBody { get; set; }
public string Layout { get; set; }
public string Result => _result.ToString();
public abstract Task ExecuteAsync();
public RawString RenderBody() => new RawString(RenderedBody);
public RawString Include(string templateName, object model = null)
{
var result = Manager.Render(templateName, model);
return new RawString(result);
}
public void WriteLiteral(string literal)
{
_result.Append(literal);
}
public void Write(string text)
{
text = HttpUtility.HtmlEncode(text);
_result.Append(text);
}
public void Write(object obj)
{
var text = obj.ToString();
if (obj is RawString)
{
_result.Append(text);
} else
{
Write(text);
}
}
public void BeginWriteAttribute(string name, string prefix, int prefixOffset, string suffix, int suffixOffset, int attributeValuesCount)
{
_result.Append(prefix);
_attributeSuffix = suffix;
}
public void WriteAttributeValue(string prefix, int prefixOffset, object value, int valueOffset, int valueLength, bool isLiteral)
{
WriteLiteral(prefix);
if (isLiteral)
{
WriteLiteral(value.ToString());
}
else {
Write(value);
}
}
public void EndWriteAttribute()
{
_result.Append(_attributeSuffix);
_attributeSuffix = null;
}
}
public abstract class TemplatePage<TModel> : TemplatePageBase
{
public TModel Model
{
get
{
return (TModel)ModelBase;
}
set
{
ModelBase = value;
}
}
}
}
using System.IO;
using System.Text;
using Microsoft.AspNetCore.Razor.Language;
namespace Razor.Mail
{
public class TemplateProjectItem : RazorProjectItem
{
private readonly string _templateText;
public TemplateProjectItem(string templateText)
{
_templateText = templateText;
}
public override string BasePath => "";
public override string FilePath => "";
public override string PhysicalPath => "";
public override bool Exists => true;
public override Stream Read()
{
var byteArray = Encoding.UTF8.GetBytes(_templateText);
return new MemoryStream(byteArray);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment