Update: Razor without MVC part II contains anonymous types

Please visit: Here to find a great rewrite of this article with a very well written project that includes a VB compiler!

In my previous post I discuss using the Razor View Engine to create views from string templates using a simple call.

string Template = "Hello, @Model.Name";
Models.User user = new Models.User() { Name = "Billy Boy" };
RazorViewEngineRender view = new RazorViewEngineRender(Template);
string Results = view.Render(user); //pass in your model

Not happy with relying on so much MVC I’ve decided to redo this and use only what is necessary to produce results.

BaseWriter

First I needed to find out the basic requirements for using Razor. I found that Razor Code Generator creates a class with a single method called Execute that overrides some base class. Other methods that are called are WriteLiteral and Write. So i created a BaseWriter class that contains these methods

public class BaseWriter {
    StringBuilder sb;

    public BaseWriter() {
        sb = new StringBuilder();
    }

    public string Output {
        get {
            return this.sb.ToString();
        }
    }

    public void WriteLiteral(string literal) {
        sb.Append(literal);
    }

    public void Write(object obj) {
        sb.Append(obj);
    }

    public virtual void Execute() { }
}

Internally I just write to a StringBuilder, just simpler for now.

Model support

Next I needed to add model support to the BaseWriter class. Simple enough to use generics.

public class BaseWriter<T> : BaseWriter {
    T Model;

    public void SetModel(T Model) {
        this.Model = Model;
    }
}

The compiler

Next I needed to take the template and convert it to an assembly. Thanks goes to Andrew Nurse for his base example. We pass in the object we’re going to use as the model for statements like @Model.Name We then alter our baseClass to add the model generic if it’s not null. The rest of the code is just a basic dynamic code compiler and it returns a CompilerResult.

private CompilerResults Compile(object Model) {
    string baseClass = "Razor.Compiler.BaseWriter" + (Model == null ? "" : "&lt;" + Model.GetType().FullName + "&gt;");

    CodeLanguageService languageService = CodeLanguageService.GetServiceByExtension(".cshtml");
    System.CodeDom.Compiler.CodeDomProvider provider = new CSharpCodeProvider();
    InlinePageParser parser = new InlinePageParser(languageService.CreateCodeParser(), new HtmlMarkupParser());
    Microsoft.WebPages.Compilation.CodeGenerator codeGenerator = languageService.CreateCodeGeneratorParserListener(ClassName, "RazorBlade.Dynamic", "object", ClassName + ".cs", baseClass);

    using (StreamReader reader = new StreamReader(new System.IO.MemoryStream(System.Text.ASCIIEncoding.ASCII.GetBytes(Template)))) {
        parser.Parse(reader, codeGenerator);
    }

    codeGenerator.GeneratedCode.Namespaces[0].Types[0].Members.RemoveAt(0);
    codeGenerator.GeneratedCode.Namespaces[0].Imports.Clear();

    System.Text.StringBuilder sb = new StringBuilder();
    using (StringWriter writer = new StringWriter(sb)) {
        provider.GenerateCodeFromCompileUnit(codeGenerator.GeneratedCode, writer, new System.CodeDom.Compiler.CodeGeneratorOptions());
    }

    CompilerParameters @params = new CompilerParameters();
    foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) {
        @params.ReferencedAssemblies.Add(assembly.Location);
    }

    @params.GenerateInMemory = true;
    @params.IncludeDebugInformation = false;
    @params.GenerateExecutable = false;
    @params.CompilerOptions = "/target:library /optimize";

    return new CSharpCodeProvider().CompileAssemblyFromSource(@params, new string[] { sb.ToString() });
}

The generated code

Here’s a sample of the generated code. It’s very basic and only includes the method Execute(); The template for this particular set of code

string Template = "Hello @Model";

namespace RazorBlade.Dynamic {
    public class bcbcebecbcff : Razor.Compiler.BaseWriter {

        public override void Execute() {
            this.WriteLiteral("Hello ");
            this.WriteLiteral(Model);
        }

    }
}

Execute

This method calls the compiler and based on the result throwing an exception on any errors. We then create an instance of our newly generated class. If we’ve passed in a model let’s call our SetModel method and then we execute and grab the output.

public string Execute(object Model) {
    CompilerResults results = Compile(Model);

    if (results.Errors.HasErrors) {
        string console = "";
        foreach (CompilerError error in results.Errors) { console += error.ErrorText + "\r\n"; }

        throw new Exception(console);
    }

    object reference = results.CompiledAssembly.CreateInstance("RazorBlade.Dynamic." + ClassName);
    if (Model != null)
        reference.GetType().InvokeMember("SetModel",
            System.Reflection.BindingFlags.Public |
            System.Reflection.BindingFlags.InvokeMethod |
            System.Reflection.BindingFlags.FlattenHierarchy |
            System.Reflection.BindingFlags.Instance |
            System.Reflection.BindingFlags.IgnoreCase, null, reference, new object[] { Model });

    reference.GetType().InvokeMember("Execute",
        System.Reflection.BindingFlags.Public |
        System.Reflection.BindingFlags.InvokeMethod |
        System.Reflection.BindingFlags.FlattenHierarchy |
        System.Reflection.BindingFlags.Instance |
        System.Reflection.BindingFlags.IgnoreCase, null, reference, null);

    var value = reference.GetType().InvokeMember("Output",
        System.Reflection.BindingFlags.Public |
        System.Reflection.BindingFlags.FlattenHierarchy |
        System.Reflection.BindingFlags.GetProperty |
        System.Reflection.BindingFlags.Instance |
        System.Reflection.BindingFlags.IgnoreCase, null, reference, null);

    this.Output = value.ToString();

    return this.Output;
}

Final code call

string Template = "Hello @Model";

Razor.Compiler.RazorBlade blade = new Razor.Compiler.RazorBlade(Template);
var results = blade.Execute("World");

This is very simple and produces good results with more complex templates. It doesn’t support any of the @parameters such as @inherits or @using. However I don’t think it’s really necessary.

Conclusion

The only additional reference this project requires is Microsoft.WebPages.Compilation.dll
This is a fairly simple class for what I initially thought would be much more complicated. The razor parser is really well done.

You probably shouldn’t use this code anywhere as I haven’t gone though and looked for any obvious errors or glaring code issues or memory-leaks.

I hope someone takes this code to the next level.

Update

By request I’ve created a really quick sample project. You can download it here.

- Ben

Kick It on DotNetKicks.com
Shout it
Digg This
Reddit This
Stumble Now!
Buzz This
Vote on DZone
Bookmark this on Delicious
Bookmark this on Technorati
Post on Twitter

  22 Responses to “Razor View Engine without MVC at all”

  1. Razor View Engine without MVC at all…

    Thank you for submitting this cool story – Trackback from DotNetShoutout…

  2. I was the one who posted the question on SO. This looks pretty good. I am having a little trouble setting it up because a) I am weak at C# and b) I am not exactly sure how you setup the project. (i.e. Razor.Compiler.RazorBlade )

    If you have a project sample that could be downloaded that would be great. If not maybe pasting in the overall project code with the definition of RazorBlade etc..

    Thanks,

    Jim

    • I’ve added a sample project to the post. Feel free to download it and try it out.

      Also, don’t forget to vote and accept answers on stackoverflow.com when the answer is helpful :)

  3. I will as soon as I get my reputation above 15. I am a SO lightweight still.

  4. Thanks again for the project download.

    I ran your sample and it works great. I then created a basic MVC project and included your Razor.Blade class and try to call it from the MVC page. It has a run-time crash on the line:

    (in complile)
    @params.ReferencedAssemblies.Add(assembly.Location);

    It says The invoked member is not supported in a dynamic assembly.

    Here is my calling code:

    public ActionResult Index()
    {
    ViewModel.Message = “Welcome to ASP.NET MVC!”;

    string Template = “Hello @Model”;
    Razor.Compiler.Blade blade = new Razor.Compiler.Blade(Template);
    var results = blade.Execute(“World”);

    ViewModel.Results = results;
    return View();
    }

    Any ideas ?

    Thanks again.

    • That’s correct. You can’t include dynamic assemblies in the compiler. By default my project includes all references that are in your project. What you’ll need to do is exclude any references that don’t have a physical location. Change line #48 of Blade.cs to
      if (assembly.ManifestModule.Name != “<In Memory Module>”)
      @params.ReferencedAssemblies.Add(assembly.Location);

      This should solve the issues for the time being. Again, this is all just off the cuff code that shouldn’t really be used without a probable rewrite with security and stability in mind :)

      BAH, I’ve updated this because of wordpress stealing my < > tags

  5. Thanks that did it. I don’t understand C# well enough to know why but it works. Anyways I was able to vote up your solution in SO finally.

    Cheers,

    Jim

    • Glad it works. There are a lot of things I still have to learn about c# too. Dynamic compilation was pretty new to me. But learning new things are what I enjoy doing so I was glad for your question.

      Thanks for the vote up – don’t forget to accept my answer if you found it to be the answer to your question.

  6. Just fyi, over the next week or so I will take this code and run some unit tests on it. Have it do millions of blade.executes and see if there are any memory leaks or other issues. I will probably also add some more error handling and maybe inject a logging module in it.

    I stepped through the code in more detail. I see what your fix is doing now with the In Memory Module. I guess there are a few choices. Just always include all of the project assemblies probably works 99% of the time. Might also make sense to add the ability to build a list of other assemblies to add as well. Just incase the project does not cover everything.

    Either way what you started here is a great start.

    Thanks again,

    Jim

    • Keep me apprised, if you don’t mind. Also, one thing to note is that the next update to MVC will probably include this functionality so don’t work too hard on it! :)

  7. The new MVC Preview 3 is out. Looks like a lot of API changes so the code above will probably change.

    http://weblogs.asp.net/scottgu/archive/2010/10/06/announcing-nupack-asp-net-mvc-3-beta-and-webmatrix-beta-2.aspx

  8. Well I have been looking through the dll’s and it looks like there is some new API’s in System.Web.Razor. It looks like all the tricks used in your example here might be now gone or moved to other dll’s.

    So I am back to square 1 for a while. :(

    • Yes there is. I’ve been reading the source for the past hour or so looking how it all works. I’m not sure when I’ll have an update though. I had a feeling they were going to break everything…I really need to update my Syntax Highlighter too – which is my first priority.

  9. [...] What you might find though, is a particularly interesting blog post by BuildStarted entitled “Razor View Engine without MVC at all” whereby he had crafted a self contained Razor compiler that didn’t required MVC. Now [...]

    • Awesome article…shame I can’t comment on it. Everyone, please visit his url above. I had planned on writing a new article with the updated setup but your article is more than adequate.

    • Nice Job. You guys rock. Thanks Matt and thanks again Ben for getting this ball rolling.

  10. Hi,

    Great piece of work :) Think I’m definitely going to be using this in future!

    One of the things I had noticed was that your are using reflection to set properties and call the Execute method. Could you not cast your newly instantiated instance back to BaseWriter and do it from there.

    I’ve refactored your work a bit based on the changes made to the Razor libaries since the new release of Web Matrix, as well as adding support for the VB syntax in Razor templates.

    Let me know what you think :)

    http://www.fidelitydesign.net/?p=208

  11. [...] wonderful blog post by Matt at http://www.fidelitydesign.net/ (which is based on my previous post here.) I looked into adding anonymous types to the compiler. Rather than start with my version of the [...]

  12. [...] Apart from its much cleaner syntax, Razor is also more flexible and it’s open source. Matthew Abbot and Ben of BuildStarted.com have written some interesting articles on how to use Razor without MVC and even without ASP.NET at all. [...]

  13. [...] Microsoft’s new Razor view engine technology can be used without MVC. This was all thanks to Ben at BuildStarted.com, who created the initial version thanks to Andrew Nurse’s code and also solved the issue of [...]

Sorry, the comment form is closed at this time.

   
© 2012 BuildStarted.com Suffusion theme by Sayontan Sinha