Last active
May 3, 2018 22:17
-
-
Save ArtemAvramenko/eb13a4bae621e029af5ddb00de75674c to your computer and use it in GitHub Desktop.
Use Microsoft.AspNetCore.Mvc.Razor to generate emails
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
namespace Razor.Mail | |
{ | |
public class RawString | |
{ | |
private readonly string _text; | |
public RawString(string text) => _text = text; | |
public override string ToString() | |
{ | |
return _text; | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"); | |
} | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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