Using Razor from a Console Application
July 7, 2010 13 Comments
I just found out about Microsoft’s new View engine Razor. As soon as I read Scott’s post about it which is great, I wanted to investigate it. One of the things that caught my attention was the fact that Scott said that the view didn’t require to be hosted in a web application, and that executing it from a console app was possible.
So I went ahead, and few hours later of Reflector, dynamic compilation and trial and error (why not?), I figured out a way to do it.
First you need to download WebMatrix, the new MS tool for building simple websites fast and easy. We need WebMatrix so that we can pull the dlls we need in order to use Razor. After installing WebMatrix you can find the necessary assemblies in the following folder: C:\Program Files (x86)\Microsoft ASP.NET\ASP.NET Web Pages\v1.0\Assemblies. In case you don’t find them there, you can perform a Publish from WebMatrix and that will copy the dlls into the bin folder. The list of dlls we are looking for is:
Microsoft.WebPages.dll
Microsoft.WebPages.Compilation.dll
Microsoft.WebPages.Configuration.dll
Microsoft.WebPages.Helpers.dll
Microsoft.WebPages.Helpers.Toolkit.dll
Next thing we need to do is add the reference to our project of the following assemblies (you can add more if you want to access from the template cshtml or vbhtml files), and don’t forget to mark them as “Copy Always”:
System.dll
System.Core.dll
System.Web.dll
System.Web.Mvc.dll
System.Data.dll
System.Web.Extensions.dll
Microsoft.CSharp.dll
Microsoft.WebPages.dll
Microsoft.WebPages.Compilation.dll
Microsoft.WebPages.Configuration.dll
Microsoft.WebPages.Helpers.dll
Microsoft.WebPages.Helpers.Toolkit.dll
Microsoft.Data.Schema.dll
The only way I have found to execute a razor file (cshtml or vbhtml) is to parse the file with the parsers (Razor uses two parsers to parse a file, a MarkupParser, and a CodeParser, in this case I will use an HtmlMarkupParser and a CSharpCodeParser) found in Microsoft.WebPages.Compilation.Parser, and then compiling the result to a temporary assembly, and extracting the type from the new assembly. So I have wrapped this in a class I called RazorFactory like this:
Snippet
public class RazorFactory
{
public List<String> ReferencedAssemblies
{
get;
private set;
}
private List<string> errors = new List<string>();
public string[] Errors
{
get
{
return errors.ToArray();
}
}
public RazorFactory()
{
ReferencedAssemblies = new List<string>();
ReferencedAssemblies.Add("System.dll");
ReferencedAssemblies.Add("System.Core.dll");
ReferencedAssemblies.Add("System.Web.dll");
ReferencedAssemblies.Add("System.Web.Mvc.dll");
ReferencedAssemblies.Add("System.Data.dll");
ReferencedAssemblies.Add("System.Web.Extensions.dll");
ReferencedAssemblies.Add("Microsoft.CSharp.dll");
ReferencedAssemblies.Add("Microsoft.WebPages.dll");
ReferencedAssemblies.Add("Microsoft.WebPages.Compilation.dll");
ReferencedAssemblies.Add("Microsoft.WebPages.Configuration.dll");
ReferencedAssemblies.Add("Microsoft.WebPages.Helpers.dll");
ReferencedAssemblies.Add("Microsoft.WebPages.Helpers.Toolkit.dll");
ReferencedAssemblies.Add("Microsoft.Data.Schema.dll");
}
public T Create<T>(string fileName) where T : class
{
using (var reader = new StreamReader(fileName))
{
return Create<T>(reader);
}
}
public T Create<T>(TextReader reader) where T : class
{
var codeParser = new CSharpCodeParser();
var markupParser = new HtmlMarkupParser();
var parser = new InlinePageParser(codeParser, markupParser);
var listener = new CSharpCodeGeneratorParserListener(
"className",
"ASP",
"System.Web.HttpApplication",
"linePragmaFileName",
typeof(T).FullName);
foreach (var n in CodeGeneratorSettings.GetGlobalImports())
listener.AdditionalImports.Add(n);
parser.Parse(reader, listener);
var option = new System.CodeDom.Compiler.CompilerParameters();
option.ReferencedAssemblies.AddRange(ReferencedAssemblies.ToArray());
option.GenerateExecutable = false;
option.GenerateInMemory = true;
var options = new Dictionary<string, string>();
options.Add("CompilerVersion", "v3.5");
var compiler = Microsoft.CSharp.CSharpCodeProvider.CreateProvider("C#", options);
var compiled = compiler.CompileAssemblyFromDom(
option,
listener.GeneratedCode);
foreach (var e in compiled.Errors)
errors.Add(e.ToString());
if (Errors.Length > 0)
return null;
else
{
var page = Activator.CreateInstance(compiled.CompiledAssembly.GetTypes().FirstOrDefault()) as T;
return page;
}
}
}
With this you can build the “WebPage” you want to execute, and set it’s properties as you choose. I have done to Base classes that will render the output in the Console or in a file. Here are the classes:
Snippet
public class ConsoleWriter : WebPage
{
public new string Model
{
get;set;
}
public override void Execute()
{
this.Context = new HttpContextWrapper(new HttpContext(new HttpRequest(null, null, null), new HttpResponse(Console.Out)));
}
public override void WriteLiteral(object o)
{
if (o != null)
Console.Write(o.ToString());
}
public override void Write(Microsoft.WebPages.Helpers.HelperResult result)
{
Write(result);
}
public override void Write(object value)
{
if (value != null)
Console.Write(value.ToString());
}
}
No big deal in the ConsoleWriter. I have added a property called Model to ilustrate that you can add as many properties as you want, and you can use these to render output.
Here is the FileWriter:
Snippet
public class FileWriter<T> : WebPage, IDisposable
{
private string filePath;
public string FilePath
{
get
{
return filePath;
}
set
{
filePath = value;
File.WriteAllText(FilePath, string.Empty);
writer = new StreamWriter(FilePath);
this.Context = new HttpContextWrapper(new HttpContext(new HttpRequest(null, "http://razor", null), new HttpResponse(writer)));
}
}
private StreamWriter writer;
public FileWriter()
{
}
public new T Model
{
get;
set;
}
public override void Execute()
{
}
public override void WriteLiteral(object o)
{
if (o != null)
writer.Write(o.ToString());
}
public override void Write(Microsoft.WebPages.Helpers.HelperResult result)
{
Write(result);
}
public override void Write(object value)
{
if (value != null)
writer.Write(value.ToString());
}
public void Dispose()
{
if (writer != null)
{
writer.Flush();
writer.Close();
writer.Dispose();
}
}
}
This is one is pretty much the same, but I have added Generics to it, and a property Model that is of type T, again to ilustrate that this is totally valid. I also chose to do this one IDisposable.
Here is how you would use these writers with our RazorFactory:
Snippet
class Program
{
static void Main(string[] args)
{
var filePath = Path.Combine(Directory.GetCurrentDirectory(), "Test.txt");
var factory = new RazorClient.RazorFactory();
factory.ReferencedAssemblies.Add("ConsoleApplication1.exe");
using (var page = factory.Create<FileWriter<string>>(filePath))
{
if (factory.Errors.Length > 0)
foreach (var e in factory.Errors)
Console.WriteLine(e);
else
{
page.FilePath = filePath + ".rzr.txt";
page.Model = "this is my model";
page.Execute();
}
}
Console.ReadKey();
}
}
Check out at how we can do page.Model = “this is my model”, this could easily be page.Model = typeof(Customer) or whatever.
Here is the Test.txt file content:
ASP.NET Web Pages makes it easy to build powerful .NET based applications for the web.
Time: @DateTime.Now.ToString()
Model: @Model
And here is the Test.txt.rzr.txt file output:
ASP.NET Web Pages makes it easy to build powerful .NET based applications for the web.
Time: 7/7/2010 4:25:24 PM
Model: this is my model
Conclusion
We have seen how to run a cshtml or a vbhtml file and generate code, and we have encapsulated that into a helper factory that returns an instance of the template. Then we have provided two way to output the generated code, plus adding some typed and generic properties.
Update: check out my other post on how to call child razor templates.
Very very cool! You beat me to it
. I’ll be posting a more detailed sample of using Razor from a Console App, but thanks for posting your initial explorations!
Great post, and was something I was initially looking to do for a project I wish to work on once Razor is released outside of WebMatrix. However when I run the code samples you’ve provided, I get an Exception saying it cannot find the compiler csc.exe, have you come across this?
Hi Tom, I haven’t come across that error. Feel free to send me the project to machadogj@gmail.com.
Cheers!
It was my fault, I put “3.5″ instead of “v3.5″ in the options!
Pingback: Razor from a console – calling child templates « G# blog
The idea of thinking of using Razor like this, by itself, fascinates me.
Great Job !
As the templates are compiled in memory, is it possible to release them to not produce memory leaks?
Hi Nitzzzu, once you load an assembly you can’t unload it, however that won’t produce memory leaks. If you want to release all the assemblies that have been generated you can kick this entire process inside it’s own application domain, and then unload the application domain. However if you run this from a console app, I don’t think it will be necessary. If you have a special scenario, or if you are planning to kick this process from a winforms application, feel free to provide me with more details.
In order to avoid memory leaks during the execution of a template, templates themselves are IDisposable so you can enclose the template variable in a using() statement.
Hope this helps!
Gus
Thanks for the fast response. I would like to use Razor as a reporting engine because it has a lot of potential, but the only problem that I see now is this “memory leak” caused by the inmemory compilation in the same app domain as the main application. I couldn’t find a good solution to this (to work both in ASP .NET and Desktop apps) as running in a separate app domain and still having access to the “model” is quite a complex scenario. It’s really sad that the dynamic assemblies cannot be unloaded.
Nitzzzu, you only need to compile the assembly the first time you are going to execute the template. In this case, since I’m using Razor from a Console app I didn’t have the need to store the dll in my disk (even though I am already working on caching the assembly).
In your case you could compile the template into an assembly and save it to the hard drive, and then add the reference of the dll to your website or desktop app. That way you won’t be compiling anything in runtime, thus being much faster, and no extra dlls will be loaded.
Hope this helps!
Pingback: Mikael Söderström
Hi Gustavo,
I couldn´t find you mail address anywhere, so I post here instead.
I used your code to create a T4 Text Templating Engine Host which uses Razor instead of the original T4 syntax.
You can find it here:
http://bit.ly/91lhdS
Good job btw!
/Mikael Söderström
good post