📜 ⬆️ ⬇️

Using Razor outside of ASP.NET

So, yesterday Microsoft released ASP.NET MVC3 RTM , which includes the new Razor presentation engine. As you probably already know, Razor does not contain any components that are specific to the web, which means that it can be used in other applications. Well, if you do not know this yet - then it's time to find out!

In this post, I will show you how to use Razor as a template engine for your needs. The source for this was the blog post Andrew Nurse "Hosting Razor outside of ASP.Net" , but this is not a direct translation.


')
For example, I will create a letter text that contains information about the order made.

The order is described by two classes, OrderModel and OrderItemModel:

public class OrderModel { public string FirstName { get; set; } public string LastName { get; set; } public List<OrderItemModel> Items { get; set; } } public class OrderItemModel { public string ProductName { get; set; } public Decimal Price { get; set; } public int Qty { get; set; } } 


The template that I will use to create an e-mail is shown below. For more information about the Razor syntax, you can get, for example, in Andrei Taritsyn 's blog post “Razor Syntax Quick Reference [Translation]” .

 , @Model.FirstName @Model.LastName! #   -  - ---------- ------ ------ ------ @for (var i = 0; i < Model.Items.Count; i++) { var item = Model.Items[i]; @String.Format( "{0} {1,-10} {2,6} {3,6} {4,6}\r\n", i + 1, @item.ProductName, @item.Price, @item.Qty, item.Price * item.Qty) } : @(((OrderModel)Model).Items.Sum(x => x.Price * x.Qty)) WBR, ACME team 


I will need a class that will be used as a base for my template:

 public abstract class TemplateBase { public TextWriter Output { get; set; } public dynamic Model { get; set; } public abstract void Execute(); public virtual void Write(object value) { this.Output.Write(value); } public virtual void WriteLiteral(object value) { this.Output.Write(value); } } 


The Output property sets the TextWriter, which receives the result of the execution of the template.

The Model property is used to pass parameters to the template.

The Execute () method in the inherited class will contain the template code.

The Write () and WriteLiteral () methods are used to output, respectively, the results of evaluating expressions and text strings. Because I do not need additional processing for the results of the calculations, nor for the lines, the code of these methods coincides.

Note: Execute (), Write () and WriteLiteral () are used by default; if necessary, you can specify other names using the GeneratedClassContext property of an instance of the RazorEngineHost class.

Now I will create a host for Razor, while indicating that my template uses C # (Razor supports C # and VB, if necessary you can use VB by passing an instance of VBRazorCodeLanguage to the RazorEngineHost constructor):

 var razorHost = new RazorEngineHost(new CSharpRazorCodeLanguage()); 


Next, I specify the name of the base class, the namespace in which the template code will be located, and the name of the template class:

 razorHost.DefaultBaseClass = typeof(TemplateBase).FullName; razorHost.DefaultNamespace = "StandaloneRazorDemo"; razorHost.DefaultClassName = "Template"; 


I add a set of namespaces that will be available in the code of the template class:

 razorHost.NamespaceImports.Add("System"); razorHost.NamespaceImports.Add("System.Collections.Generic"); razorHost.NamespaceImports.Add("System.Linq"); razorHost.NamespaceImports.Add("System.Text"); 


And finally, I create a template engine:

 var razorEngine = new RazorTemplateEngine(razorHost); 


Now I will try to make out my template:

 var templateText = File.ReadAllText("template.txt"); GeneratorResults generatorResults = null; using (var reader = new StringReader(templateText)) { generatorResults = razorEngine.GenerateCode(reader); } 


The Success property of the GeneratorResults class indicates how successfully the parsing was performed. If the parsing problems, I show a list of errors:

 if (!generatorResults.Success) { foreach (var error in generatorResults.ParserErrors) { Console.WriteLine( "Razor error: ({0}, {1}) {2}", error.Location.LineIndex + 1, error.Location.CharacterIndex + 1, error.Message); } throw new ApplicationException(); } 


If parsing is completed successfully, the GeneratedCode property contains the DOM tree, which can then be used to generate code:

 return generatorResults.GeneratedCode; 


Now I need to compile the template:

 private static string CompileTemplate(CodeCompileUnit generatedCode) { var codeProvider = new CSharpCodeProvider(); #if DEBUG using (var writer = new StreamWriter("out.cs", false, Encoding.UTF8)) { codeProvider.GenerateCodeFromCompileUnit( generatedCode, writer, new CodeGeneratorOptions()); } #endif var outDirectory = "temp"; Directory.CreateDirectory(outDirectory); var outAssemblyName = Path.Combine(outDirectory, String.Format("{0}.dll", Guid.NewGuid().ToString("N"))); var refAssemblyNames = new List<string>(); refAssemblyNames.Add(new Uri(typeof(TemplateBase).Assembly.CodeBase).AbsolutePath); refAssemblyNames.Add("System.Core.dll"); refAssemblyNames.Add("Microsoft.CSharp.dll"); var compilerResults = codeProvider.CompileAssemblyFromDom( new CompilerParameters(refAssemblyNames.ToArray(), outAssemblyName), generatedCode); if (compilerResults.Errors.HasErrors) { var errors = compilerResults .Errors .OfType<CompilerError>() .Where(x => !x.IsWarning); foreach (var error in errors) { Console.WriteLine("Compiler error: ({0}, {1}) {2}", error.Line, error.Column, error.ErrorText); } throw new ApplicationException(); } return outAssemblyName; } 


The above method returns the name of the assembly that contains the compiled template.

The code contained in #if DEBUG ... #endif is used for debugging and allows you to see what the pattern has become after all the manipulations performed on it.

All I need to do now is to load the assembly, create an instance of the template class and "execute" it:

 var assembly = Assembly.LoadFrom(outAssemblyName); var type = assembly.GetType("StandaloneRazorDemo.Template", true); var template = Activator.CreateInstance(type) as TemplateBase; using (var writer = new StringWriter()) { template.Output = writer; template.Model = GetModel(); template.Execute(); File.WriteAllText("out.txt", writer.ToString(), Encoding.UTF8); } 


The GetModel () method is defined as follows:

 private static OrderModel GetModel() { var model = new OrderModel { FirstName = "", LastName = "" }; model.Items = new List<OrderItemModel>(); model.Items.Add(new OrderItemModel { ProductName = "Apple", Price = 4.95m, Qty = 1 }); model.Items.Add(new OrderItemModel { ProductName = "Kiwi", Price = 9.95m, Qty = 2 }); return model; } 


Now the file "out.txt" contains the result of the "execution" of the template:

 ,  ! #   -  - ---------- ------ ------ ------ 1 Apple 4,95 1 4,95 2 Kiwi 9,95 2 19,90 : 24,85 WBR, ACME team 


That's all!

Sample code:
StandaloneRazorDemo.zip

Source: https://habr.com/ru/post/111837/


All Articles