Wednesday, October 19, 2011

Using Roslyn to implement an MVC Razor view engine

Update 12/29/2011: the Roslyn CTP is now available on NuGet, so it's no longer necessary to install it before running this sample!

Note: the code for this view engine sample is on Github.

The C# team has just announced the public availability of the first Roslyn CTP. See their post here, and download it from here. I really hope they can make it available on NuGet soon, but right now it’s not there, so you’ll have to run their setup. Sorry!

As you’ve probably heard from various conferences earlier this year, Roslyn offers a compiler as a service for C# and VB. Since we do a lot of compilation in ASP.NET land, I figured I’d play around with trying write an MVC view engine that uses it instead of the standard compilation path.

Word of warning: the Roslyn CTP is still very rough and is missing a lot of key features, like dynamic, anonymous types, indexers and using statements (get the full list here). So while I did get something working, the language limitations prevent it from being useful in any real scenario. This is just an exercise to see how far we can get. Lower your expectations! :)

Why would we want to do this

When you have a standard MVC project, compilation happens at two different levels:

  • Your Controllers, Models, and most of your C# code get compiled by msbuild (or Visual Studio) into a single assembly which ends up in the ‘bin’ folder
  • All the Views (whether .aspx or .cshtml) get compiled dynamically at runtime by ASP.NET.

One drawback of compiling views at runtime is that it’s pretty slow. And since it’s slow, ASP.NET tries really hard to save assemblies to disk so it can reuse them across AppDomain cycles. Those assemblies all go under the infamous ‘Temporary ASP.NET Files’ folder. There is a huge amount of complexity to make this work, with settings like batching which can either help or hurt depending on the situation.

One thing I’ve been working on to avoid this dynamic compilation is RazorGenerator, which lets you precompile your views into the same assembly as your controllers. This works quite nicely, but it does have one big drawback: you can’t just update a view and have it get picked up at runtime. Instead, you need to rebuild using msbuild (or VS), just like you would when you change a controller file.

What would be nice is to be able to support dynamic compilation of the views, but with a much lighter system then what the standard ASP.NET Build Manager provides. Enter Roslyn!

Compile views using Roslyn: fast and lightweight

The main reason that the standard build manager is pretty slow is that it goes through CodeDom, which launching csc.exe for every compilation. csc.exe is actually very fast at compiling C# code, but the fact that we have to pay for the csc process startup time each time we compile anything ends up making things slow.

By contrast, Roslyn gives us an API to compile code in memory, without ever having to launch another process, making things much faster. In fact, it is so fast that the incentive that we had to preserve compiled assembly in ‘Temporary ASP.NET Files’ mostly disappears.

Instead, we can take a much simpler approach: whenever we need to compile a view, we just compile it on the fly in memory using Roslyn, and then cache it for the lifetime of the AppDomain. But we never need to cache it to disk, and generally don’t use the disk at all.

In preliminary tests, I have measured the perf of compiling pages using Roslyn to be more than 50 times faster than doing it via CodeDom. So it’s looking quite promising!

So to summarize, the benefits of using Roslyn to implement a view engine are:

  • Fast dynamic compilation
  • No need to cache assemblies to disk, leading to a much simpler and lighter weight system.
  • New shiny thing! :)

More detail about the code

The code for my sample view engine is on Github (https://github.com/davidebbo/RoslynRazorViewEngine), so I’ll mostly let you check it out there. All the interesting code is in RoslynRazorViewEngine.cs.

Here are the main steps that it goes through to turn a Razor file into an Assembly:

  1. First it uses the Razor Engine to generate a CodeCompileUnit from the Razor file.
  2. It then uses CodeDom to turn the CodeCompileUnit into C# source code. Note that we only use CodeDom as a code generator here, and not to actually compile anything.
  3. We then use Roslyn to compile the course code into a byte[]. That byte array is basically an in memory copy of what would normally be a .dll file.
  4. Finally, we call Assembly.Load to load that byte[] into a runtime Assembly.

How restrictive are the limitations in the Roslyn CTP?

As I mentioned above, there are lots of limitations, which make this little more than a proof of concept.

To begin with, it doesn’t support dynamic, which MVC uses pretty heavily. By default, MVC views extend WebViewPage<dynamic>, so I had to add ‘@model object’ at the top of my test view to get around that.

Then there is ViewBag, which is also dynamic, and allows writing things like ‘@ViewBag.Message’. I tried replacing that by ‘@ViewData["Message"]’, only to find out that indexers were not supported either. Duh!

And then it doesn’t support anonymous objects, which MVC uses quite a bit...

So don’t even think of trying to use this for anything real at this time. Still, the approach feels pretty sound, and whenever Roslyn becomes more feature complete, I have good hope that it can help us improve the ASP.NET compilation system.

17 comments:

  1. I'm assuming at this stage it also can't handle layouts/sections and helpers? Under the hood these are generated as lambda statements? Still though, something to look forward to!

    ReplyDelete
  2. @davidebbo
    somebody should make a statue of you at Redmond Campus or I will. Nice touch there man! Thanks a lot, very helpful.

    ReplyDelete
  3. @Matthew: right, that probably wouldn't work yet, but will eventually.

    ReplyDelete
  4. @Tugberk: I've been here so long, I'm probably close to becoming a living statue anyway :)

    ReplyDelete
  5. very promising, very cool to see Roslyn getting leveraged in something 'as close' as ASP.NET

    ReplyDelete
  6. @Matthew: Roslyn does support lambda expressions, so sections and helpers should work (although I haven't tried them)

    ReplyDelete
  7. Hi David, Thanks for this, but what about this one here http://blogs.msdn.com/b/davidebb/archive/2010/10/27/turn-your-razor-helpers-into-reusable-libraries.aspx

    What if we compile the views using the tool you already gave.

    thanks

    ReplyDelete
  8. @parminder: RazorGenerator is still a great tool to use. I have a paragraph referring to it above.

    ReplyDelete
  9. @David,
    Thanks a lot. Your work and work from here http://www.chrisvandesteeg.nl/2010/11/22/embedding-pre-compiled-razor-views-in-your-dll/comment-page-2/#comment-80141

    is really great. I am using it crazily.

    Thanks
    Parminder

    ReplyDelete
  10. @Parminger thanks for the good feed back! :) See Roslyn based thing is just an experiment at this point, and is not usable for anything real, while RazorGenerator is fully usable today. So they're not really two alternatives to choose from.

    ReplyDelete
  11. @David,

    Using Razor class generator, I should assume my site will be bit faster as the views are in the dll file. Also, Its easy to detect errors at compile time, if a model class is changed and view is not updated.

    Regards
    Parminder

    ReplyDelete
  12. @David,

    Bit of feedback here. I have three projects Host, plugin1 and plugin2, Host project is just referring to both the other projects. In both the plugins I have HomeController and View which are using RazorGenerator .I have register both the controllers using namespaces.

    routes.MapRoute(
    "Default", // Route name
    "plugin1/{action}/{id}", // URL with parameters
    new { controller = "Home", action = "Index", id = UrlParameter.Optional }, // Parameter defaults
    new string[] { "Plugin1.Controllers" }
    );

    routes.MapRoute(
    "Default-2", // Route name
    "plugin2/{action}/{id}", // URL with parameters
    new { controller = "Home", action = "Index", id = UrlParameter.Optional }, // Parameter defaults
    new string[] { "Plugin2.Controllers" }
    );


    now if i navigate to http://localhost/plugin1/index
    or http://localhost/plugin2/index

    it always return same view. sometime first one, sometime second one.

    Can you help here, how can I fix it.

    Thanks
    Parminder

    ReplyDelete
  13. @parminger: this post is about the Roslyn approach, so I'd like to avoid discussions on other topics. For RazorGenerator, the best place to discuss is the forum on http://razorgenerator.codeplex.com/

    ReplyDelete
  14. David

    Here is another database that is compatible with Mono:
    http://www.kellermansoftware.com/p-43-ninja-net-database-pro.aspx

    ReplyDelete
  15. Hi David
    First of all thanks for such wonderful article.
    But I am still confuse with codeDom and Roslyn?
    Why new Roslyn when codeDom was there?
    I have very simple requirement i have parameters from which i have to generate classes, methods,....etc..? which one should i go for?
    Thanks!

    ReplyDelete
  16. @Vishal: they can actually be complementary: CodeDom can be used to generate source code, and Roslyn to compile it dynamically. In fact, that's exactly what happens in my code above. But here, CodeDom is only used to generate the code and not to compile it.

    ReplyDelete