Using Razor from a Console Application

07.07.2010 23:42Comments
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.NETASP.NET Web Pagesv1.0Assemblies. 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.

comments powered by Disqus