Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using Razor outside of MVC in .NET Core

I would like to use Razor as a templating engine in a .NET console application that I'm writing in .NET Core.

The standalone Razor engines I've come across (RazorEngine, RazorTemplates) all require full .NET. I'm looking for a solution that works with .NET Core.

like image 423
Christof Jans Avatar asked Jul 07 '16 13:07

Christof Jans


People also ask

Does Razor use MVC?

Razor is a markup syntax that lets you embed server-based code into web pages using C# and VB.Net. It is not a programming language. It is a server side markup language. Razor has no ties to ASP.NET MVC because Razor is a general-purpose templating engine.

Can you mix Razor pages and MVC?

You can add support for Pages to any ASP.NET Core MVC app by simply adding a Pages folder and adding Razor Pages files to this folder.

Should I learn Razor pages or MVC?

MVC is good for those web application which has lots of dynamic server pages, REST APIs and AJAX call. For basic web pages and simple user input forms, we should go for Razor pages. Razor pages are similar to ASP.Net web forms in the context of structure, except page extension.


2 Answers

Here is a sample code that only depends on Razor (for parsing and C# code generation) and Roslyn (for C# code compilation, but you could use the old CodeDom as well).

There is no MVC in that piece of code, so, no View, no .cshtml files, no Controller, just Razor source parsing and compiled runtime execution. There is still the notion of Model though.

You will only need to add following nuget packages: Microsoft.AspNetCore.Razor.Language (tested with v5.0.5), Microsoft.AspNetCore.Razor.Runtime (tested with v2.2.0) and Microsoft.CodeAnalysis.CSharp (tested with v3.9.0) nugets.

This C# source code is compatible with .NET 5, NETCore 3.1 (for older versions check this answer's history), NETStandard 2 and .NET Framework. To test it just create a .NET framework or .NET core console app, paste it, add the nugets, and create the hello.txt file by hand (it must be located aside the executables).

using System; using System.IO; using System.Reflection; using System.Threading.Tasks; using Microsoft.AspNetCore.Razor.Hosting; using Microsoft.AspNetCore.Razor.Language; using Microsoft.AspNetCore.Razor.Language.Extensions; // needed or not depends on .NET version using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp;  namespace RazorTemplate {     class Program     {         static void Main(string[] args)         {             // points to the local path             var fs = RazorProjectFileSystem.Create(".");              // customize the default engine a little bit             var engine = RazorProjectEngine.Create(RazorConfiguration.Default, fs, (builder) =>             {                 // InheritsDirective.Register(builder); // in .NET core 3.1, compatibility has been broken (again), and this is not needed anymore...                 builder.SetNamespace("MyNamespace"); // define a namespace for the Template class             });              // get a razor-templated file. My "hello.txt" template file is defined like this:             //             // @inherits RazorTemplate.MyTemplate             // Hello @Model.Name, welcome to Razor World!             //              var item = fs.GetItem("hello.txt", null);              // parse and generate C# code             var codeDocument = engine.Process(item);             var cs = codeDocument.GetCSharpDocument();              // outputs it on the console             //Console.WriteLine(cs.GeneratedCode);              // now, use roslyn, parse the C# code             var tree = CSharpSyntaxTree.ParseText(cs.GeneratedCode);              // define the dll             const string dllName = "hello";             var compilation = CSharpCompilation.Create(dllName, new[] { tree },                 new[]                 {                     MetadataReference.CreateFromFile(typeof(object).Assembly.Location), // include corlib                     MetadataReference.CreateFromFile(typeof(RazorCompiledItemAttribute).Assembly.Location), // include Microsoft.AspNetCore.Razor.Runtime                     MetadataReference.CreateFromFile(Assembly.GetExecutingAssembly().Location), // this file (that contains the MyTemplate base class)                      // for some reason on .NET core, I need to add this... this is not needed with .NET framework                     MetadataReference.CreateFromFile(Path.Combine(Path.GetDirectoryName(typeof(object).Assembly.Location), "System.Runtime.dll")),                      // as found out by @Isantipov, for some other reason on .NET Core for Mac and Linux, we need to add this... this is not needed with .NET framework                     MetadataReference.CreateFromFile(Path.Combine(Path.GetDirectoryName(typeof(object).Assembly.Location), "netstandard.dll"))                 },                 new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); // we want a dll               // compile the dll             string path = Path.Combine(Path.GetFullPath("."), dllName + ".dll");             var result = compilation.Emit(path);             if (!result.Success)             {                 Console.WriteLine(string.Join(Environment.NewLine, result.Diagnostics));                 return;             }              // load the built dll             Console.WriteLine(path);             var asm = Assembly.LoadFile(path);              // the generated type is defined in our custom namespace, as we asked. "Template" is the type name that razor uses by default.             var template = (MyTemplate)Activator.CreateInstance(asm.GetType("MyNamespace.Template"));              // run the code.             // should display "Hello Killroy, welcome to Razor World!"             template.ExecuteAsync().Wait();         }     }      // the model class. this is 100% specific to your context     public class MyModel     {         // this will map to @Model.Name         public string Name => "Killroy";     }      // the sample base template class. It's not mandatory but I think it's much easier.     public abstract class MyTemplate     {         // this will map to @Model (property name)         public MyModel Model => new MyModel();          public void WriteLiteral(string literal)         {             // replace that by a text writer for example             Console.Write(literal);         }          public void Write(object obj)         {             // replace that by a text writer for example             Console.Write(obj);         }          public async virtual Task ExecuteAsync()         {             await Task.Yield(); // whatever, we just need something that compiles...         }     } } 
like image 96
Simon Mourier Avatar answered Oct 13 '22 18:10

Simon Mourier


Recently I've created a library called RazorLight.

It has no redundant dependencies, like ASP.NET MVC parts and can be used in console applications. For now it only supports .NET Core (NetStandard1.6) - but that's exactly what you need.

Here is a short example:

IRazorLightEngine engine = EngineFactory.CreatePhysical("Path-to-your-views");  // Files and strong models string resultFromFile = engine.Parse("Test.cshtml", new Model("SomeData"));   // Strings and anonymous models string stringResult = engine.ParseString("Hello @Model.Name", new { Name = "John" });  
like image 21
Toddams Avatar answered Oct 13 '22 18:10

Toddams