Original Article: https://carljohansen.wordpress.com/2020/05/09/compiling-expression-trees-with-roslyn-without-memory-leaks-2/
I register handlebars helper for my codes that could be editable from text file.
Aim: Invoke public methods with TemplateHelper attribute.
Example Text File that contains C# Code:
using HandlebarsDotNet;using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;
namespace RosylnCompilerDemo{ public class DigitalInsertHandlebarsHelpers { [TemplateHelper] public void RegisterPriceWhole(IHandlebars _handlebars) { _handlebars.RegisterHelper("Price.Whole", (writer, context, arguments) => { if (!arguments.Any()) { throw new HandlebarsException("Correct Format: {{#Price.Whole decimalPrice seperator}}, {{Price.Whole decimalPrice}}"); } var price = arguments.At<string>(0); if (!string.IsNullOrWhiteSpace(price)) { string seperator = ","; if (arguments.Length == 2) { seperator = arguments.At<string>(1); } price = price.Split(seperator).FirstOrDefault(); } writer.WriteSafeString($"{price}"); }); }
[TemplateHelper] public void RegisterPriceFraction(IHandlebars _handlebars) { _handlebars.RegisterHelper("Price.Fraction", (writer, context, arguments) => { if (!arguments.Any()) { throw new HandlebarsException("Correct Format: {{#Price.Fraction decimalPrice seperator}}, {{Price.Whole decimalPrice}}"); } var price = arguments.At<string>(0); if (!string.IsNullOrWhiteSpace(price)) { string seperator = ","; if (arguments.Length == 2) { seperator = arguments.At<string>(1); } price = price.Split(seperator).LastOrDefault(); } writer.WriteSafeString($"{price}"); }); } }}
Code File:
using HandlebarsDotNet;using Microsoft.CodeAnalysis;using Microsoft.CodeAnalysis.CSharp;using System.Reflection;using System.Runtime.Loader;
namespace RosylnCompilerDemo{ public class TemplateHelperOperations { private class CollectibleAssemblyLoadContext : AssemblyLoadContext, IDisposable { public CollectibleAssemblyLoadContext() : base(true) { }
protected override Assembly Load(AssemblyName assemblyName) { return null; }
public void Dispose() { Unload(); } }
public static void RegisterHelpers(string code, IHandlebars handlebars) { var compilationOptions = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary);
List<Assembly> assemblies = AppDomain.CurrentDomain.GetAssemblies().ToList();
var references = assemblies.Where(q => !string.IsNullOrWhiteSpace(q.Location)) .Select(q => MetadataReference.CreateFromFile(q.Location)).ToList();
var syntaxTree = SyntaxFactory.ParseSyntaxTree(code); var assemblyName = $"TempAssembly{Guid.NewGuid()}"; var compilation = CSharpCompilation.Create(assemblyName) .WithOptions(compilationOptions) .AddReferences(references) .AddSyntaxTrees(syntaxTree);
using (var assemblyLoadContext = new CollectibleAssemblyLoadContext()) using (var ms = new MemoryStream()) { var emitResult = compilation.Emit(ms); if (emitResult.Success) { ms.Seek(0, SeekOrigin.Begin);
// Load the compiled assembly var assembly = Assembly.Load(ms.ToArray());
// Execute the library code var helperMethods = assembly.GetTypes() .Where(q => q.IsClass && q.IsPublic) .Select(q => new { ClassName = q.FullName, Methods = q.GetMethods().Where(m => m.IsPublic && m.GetCustomAttributes(typeof(TemplateHelperAttribute), false).FirstOrDefault() != null) }) .Where(q => q?.Methods?.Any() ?? false) .ToList();
foreach (var cls in helperMethods.Where(q => q.Methods?.Any() ?? false).GroupBy(q => q.ClassName)) { if (!string.IsNullOrWhiteSpace(cls.Key)) { var libClassType = assembly.GetType(cls.Key); var libraryInstance = Activator.CreateInstance(libClassType); foreach (var item in cls) { foreach (var method in item.Methods) { method.Invoke(libraryInstance, new object[] { handlebars }); } } } } } else { foreach (var diagnostic in emitResult.Diagnostics) { Console.WriteLine(diagnostic.ToString()); } } } } }
}