Turn your Razor helpers into reusable libraries


Note: the generator has evolved since this post. Although the post is still worth reading, please go to http://razorgenerator.codeplex.com/ for the most up to date doc.

The first blog post I ever wrote was titled “Turning an ascx user control into a redistributable custom control”.  It was almost exactly five years ago, and it still gets a lot of hits today.  And interestingly, this new blog post is about solving essentially the same problem, but with a much nicer Razor based solution than was available at the time.

The general issue we’re trying to solve is to encapsulate reusable pieces of UI.  Unfortunately, this has typically meant choosing between two approaches, each having their pros and cons (this mirrors the intro from my old post):

  1. Custom code in a library project: this makes it easy to produce a binary that can be used in multiple projects without having to keep source files around.  But on the downside, it’s painful to author rendering logic without a view engine language
  2. Using a non-precompiled markup file: in the WebForms world, that meant an ascx file, while in the Razor world, it means a .cshtml file.  This makes it easy to write rendering logic using ascx or Razor syntax.  But the drawback is that it’s hard to turn into a reusable library.

Here, I will show you have you can have the best of both worlds in the Razor world: write your helpers using the powerful Razor declarative helpers syntax, while still being able to build them into a reusable library.

 

The quick ‘getting started’ guide

 

If you don’t care about the details of how this works and just want to use it, here is what you need to know to run it.

  • Go to the VS Extension Gallery and install the Razor Single File Generator.

image

 

  • Here you may need to restart VS
  • In a Library project, create a Razor file (.cshtml extension)
  • Under its properties, set the Build Action to None, and set the custom tool to RazorClassGenerator

image

  • Define various @helper methods in the cshtml file.  When you save it, it’ll produce a nested .cs file, e.g.

image

  • Reference the library from your Razor view (in an MVC3 or Web Pages app) and use the helpers!

And if you’re looking for the source code, it’s all on CodePlex: http://razorgenerator.codeplex.com/

 

What are Razor declarative helpers?

 

Scottgu introduced the concept in his Razor post, under the “Declarative HTML Helpers” section.  Here is an example:

@helper WriteList(string[] items) {
    <ul>
        @foreach (var s in items) {
            <li>
                @s
            </li>
        }
    </ul>
}

Here, we are using the powerful Razor syntax to define what our helper will output.  The code in the method is basically the same thing as you write in regular Razor rendering logic, but the fact that it’s inside an @helper method turns it into a declarative helper.

Normally, those @helper methods must live either in your view itself, or in a .cshtml file in App_Code.  When it’s in the view itself, it’s only usable within that one view, while when it’s in App_Code, it’s usable from anywhere in your app.  But in either case, it really isn’t very reusable in the sense that you can’t easily turn it into a library of helpers that you can use in any app without having to carry the .cshtml file.

 

A VS Single File Generator to the rescue

 

If you’ve ever used T4 (which I’ve blogged quite a bit about), then you pretty much know what a Single File Generator is.  It’s something that you can attach to a file in your project, such that it generates another file underneath it.

In this case, the file we attach a SingleFileGenerator to is the .cshtml file, and what it generates is the source code that the Razor engine produces from it.

Writing a VS Single File Generator may seem scary, but luckily there is a good sample in the SDK: http://code.msdn.microsoft.com/sfgdd.  In fact, most of the code in my Razor generator is directly copied from this.  The only place that has interesting code that’s specific to Razor is the RazorClassGenerator.GenerateCode() method.  Here is the key code (simplified for brevity):

// Determine the project-relative path
string projectRelativePath = InputFilePath.Substring(appRoot.Length);

// Turn it into a virtual path by prepending ~ and fixing it up
string virtualPath = VirtualPathUtility.ToAppRelative("~" + projectRelativePath);

// Create the same type of Razor host that's used to process Razor files in App_Code
var host = new WebCodeRazorHost(virtualPath, InputFilePath);

// Set the namespace to be the same as what's used by default for regular .cs files
host.DefaultNamespace = FileNameSpace;

// Create a Razor engine nad pass it our host
var engine = new RazorTemplateEngine(host);

// Generate code
GeneratorResults results = null;
using (TextReader reader = new StringReader(inputFileContent)) {
    results = engine.GenerateCode(reader);
}

// Then results.GeneratedCode has the CodeDom CodeCompileUnit that the generator needs

So it’s all pretty simple.  In essence, all it does is give the content of the Razor file to the Razor engine and asks it to generate the right code for it.

 

Possible areas of improvement

 

The most obvious pain point when using this is that you need to manually set the custom tool to RazorClassGenerator.  It should be relatively easy to add behavior to the VSIX that would add a right click option on .cshtml files that would set this up.

Another potentially very cool thing is to not only use this to precompile @helpers, but also real Views.  This is trickier because it requires some logic that will allow the view engine to find the precompiled Views, but it can certainly be done.

Comments (41)

  1. Adrian Grigore says:

    Great post, thanks for sharing!

    Two ideas:

    – About precompilation of views: This could easily be done when using the T4MVC template since that takes care of finding all the views for you.

    – Wouldn't it be great if we had an online library where developers can exchange and rate reusable templates? AJAX components take a lot of fine tuning, and it's a pitty having every developer re-invent the wheel.

  2. davidebb says:

    @adrian: not sure I understand your T4MVC idea. If you contact me offline we can discuss!

  3. Adrian Grigore says:

    @David: Sorry, my mistake. I thought the problem was to find the views that should be precompiled, whereas you actually meant how to make the view engine use the precompiled views instead of their sources.

  4. Jeffrey Palermo says:

    Sounds very similar to the view-management concept in Portable Areas within MvcContrib.  I love the idea, and I hope it makes it into the product.

  5. Dorin Andreica says:

    Great job implementing this generator!

    A small improvement I'd like to suggest is to add the GeneratedCode attribute to the compiled helper class, so that code coverage and code quality tools ignore the generated code.

  6. davidebb says:

    @Dorin: thanks for the suggestion, I just made that change and pushed a new version to the VS gallery.

  7. Thanigainathan says:

    This is little bit confusing with the already available helpers. Can you please give one scenario where we can create a helper and use them. I mean some practical application.

    Thanks,

    Thani

  8. davidebb says:

    @Thanigainathan: the project on bitbucket (mentioned above) has a small sample you can try.

  9. Konstantin says:

    Scott Gu wrote that it's possible to place helpers into Views/Helpers directory. But it doesn't seem to work for me…

  10. Chris van de Steeg says:

    David, very interesting! I was also looking for some way to precompile razor views. I'm sure MS has such a tool already: just take a look at system.web.webpages.administration.dll It's full of compiled cshtml views. The answer to your problem is in there as well though: they use PageVirtualPathAttribute on the generated class.

    Apparently the RazorViewEngine takes this attribute into account when searching for views if the virtualpath providers don't return anything. I tried asking Haacked and Scott Guthrie how the WebPages team did this, but did not get an answer as of yet. Perhaps you being closer to the fire (hence, I even thought you were on the same team ;)) could help finding out what tool the WebPages team uses for this precompilation.

    If there is no such tool, I will take your class and extend it to compile real views somewhere this week (if noone beats me to it :))

  11. davidebb says:

    @Chris: yes, I actually wrote the code that you're referring to, and in fact the code in this post started from the same base (though I took out the PageVirtualPathAttribute logic).  But note that the PageVirtualPathAttribute logic as it currently exists only works for ASP.NET Web Pages (i.e. WebMatrix), and not for MVC views.  I actually prototyped doing it for MVC views earlier, but haven't had time to play with it since.  But it is definitely something worth looking into 🙂

  12. Martin says:

    Hi David,

    This looks interesting…

    Is there an issue with making this Extension available in VS2010 Express editions (C# and WebDev)?

    (I see that the VSIX Manifest doesn't include Express Editions.)

    Thanks,

    Martin

  13. davidebb says:

    @Martin: I just didn't try that. If simply adding it to the manifest allows it to work, we should change it indeed.  Please let me know.

  14. Martin says:

    Hi David,

    I think Express support can be added as follows to the manifest:

       <SupportedProducts>

         <VisualStudio Version="10.0">

           <Edition>Ultimate</Edition>

           <Edition>Premium</Edition>

           <Edition>Pro</Edition>

           <Edition>Express_All</Edition>

        </VisualStudio>

       </SupportedProducts>

    This is my reference:

    msdn.microsoft.com/…/ee822857.aspx

    It would be great if this could be added.

    Thanks,

    Martin

  15. davidebb says:

    @Martin: I tried it and unfortunately it doesn't work. When I add that, I can no longer upload the extension to the VS gallery. It fails with "You need to obtain an exception to upload a tool or control that supports the Visual Studio Express SKUs".  I think this is a deliberate limitation that they added to the Express versions.

  16. Chris van de Steeg says:

    Ok, got it working for mvc views now. Encountered a lot of Internals again 🙁

    I hacked together a t4 template, will now use your code to create a IVsSingleFileGenerator out of that in the next days….

  17. davidebb says:

    @Chris: it might be worth blogging your results when you are ready!

  18. Martin says:

    OK – that's unfortunate.

    I'm wondering whether there is a way to get this working without the Extension Manager.

    Looks like there is a requirement to place the DLL in a particular folder and make some Registry settings.

  19. Jason says:

    How does this work with an automated build?  Looking through the project I saw the compiled item referencing the generator, but didn't see the generator itself included anywhere.  This is something I could see causing problems for an automated build process.  Do you have any tips for getting this included into a project so that it could easily be shared between developers on for a command-line build process?

  20. Chris van de Steeg says:

    Hmm, now I've got it working, I'm starting to wonder if it wouldn't be a better option to just embed the source of the views, and use a VirtualPathProvider to serve those to the normal ViewEngines and the BuildManager to be compiled at runtime.

    There could be stuff in the web.config for example that changes the behavior of the views for example, that would not work for compiled views.

    What's your opinon about that? What advantages would pre-compilation have aside from a little performance win at the application startup?

  21. davidebb says:

    @Jason: it works just like T4 templates.  The idea is that the .cs files are generated at design time while you change and save the cshtml files.  The generated .cs files are then part of the project, so there is no need for the generator at build time.  So this should not be an issue at all.

  22. davidebb says:

    @Chris: one big potential benefit of pre-compilation is that it could allow you to unit test your views in a much cleaner way than is possible today, since they're just standard classes in your project rather than things that require runtime magic.

  23. @davidebb says:

    Ah, yeah,good one: just needed an extra push to finish it up 🙂

  24. Chris van de Steeg says:

    Ok, finished it: http://goo.gl/KffWq

  25. Jason says:

    Of course.  Thanks, that makes perfect sense!

  26. cshtml says:

    The mapjects engine is implemented on this cshtml, and are they're using azor with this. I am having trouble razor syntax highlighter, can someone help…

  27. Chris says:

    Hi,

    I've just tried using this, but it fails for me two ways – maybe I've done something wrong…

    Firstly, if I include a @{} block in the @helper method then the C# generated is invalid. There is a WriteTo(@__razor_helper_writer, ); line that has a missing argument. This occurs even if the code block is empty.

    Secondly, the generated class inherits from System.Web.WebPages.HelperPage which means that no Html helper methods work, as the Html member is of the wrong type.

    Please let me know if I've done something obviously wrong.

    Chris

  28. davidebb says:

    @Chris: you don't need any @{ } blocks top level in the method, since it starts out being code. You only need this inside tags.

    For the Html helper, simplest workaround is to pass it explicitly, e.g.

    @helper RenderAboutLink(System.Web.Mvc.HtmlHelper Html) {

       @Html.ActionLink("About link from helper", "About", "Home");

    }

  29. Chris says:

    Thanks David.

    It's working well now.

  30. Vince Panuccio says:

    I know I'm a bit late in saying this, but why can't you just create a whole bunch of extension methods and place that class within the System.Web.Mvc.Html namepspace? Then you wouldn't have to include a custom namespace in each view, the extensions could be put in to a library and you wouldn't have to use the @helper syntax any more.

    Thoughts?

  31. davidebb says:

    @Vince: that's a valid point. Could you open a bug on razorgenerator.codeplex.com to track this. Thanks!

  32. Ranjan says:

    David,  I have converted the same example that you have mentioned in What are Razor declarative helpers?

    to a re-usable library ..  you can refer my article in codeproject http://www.codeproject.com/…/MVC3CustomControl.aspx

  33. davidebb says:

    @Ranjan: good article. But note that it's mentioning the old version of the generator, and things have changed some, so you may want to update.

  34. Vairam Vairaperumal says:

    Great tool, please update the screen shot and this post content to current version 1.4.3 for RazorGenerator CustomTool=RazorGenerator

  35. davidebb says:

    @Vairam: I'm too lazy to update the screen shot (it's harder than text!), but I do have a note at the top pointing to the codeplex site for the latest info 🙂

  36. Deepika says:

    I am trying to load external CSS file into the main layout and use DLL but it is not working.

    Do I need to do anything for loading external css or scripts in the precompiled views

  37. davidebb says:

    @Deepika please post all RazorGenerator questions to razorgenerator.codeplex.com. Thanks!

  38. Anand says:

    Hi David, can't thank you enough for this tip, although I have been quite dumb not to have discovered it for all these years I have been learning MVC.

    I am trying something similar: build the markup generation in helper methods and move them off to a separate class library. I am sure this post will be of tremendous help.

  39. Kostas says:

    It would be VERY nice if there were some benchmarks comparing DLL-packed vs "simple views" on these metrics:

    1) HTML rendering time. (I guess it should be the same, but better safe than sorry)

    2) Project build time. (This should be longer than usual, I guess)

    One extra advantage of this approach is that deploying a simple DLL is WAY simpler than deploying hundreds of views with their directory structure. If the same could be done for all resources (JS, images, etc) it would be even better for lazy people like me.

    Perhaps it even makes defacing a site even more difficult! 😀

  40. David Ebbo says:

    @Kostas: sorry, I don't have benchmarks available. I wouldn't think the build time should be measurably slower. The C# compiler is pretty fast, and having an extra 20-ish small .cs files in there won't affect it.

    I wouldn't bet to hard on the defacing benefit! 🙂

    BTW, note that the better place for all RazorGenerator discussions is razorgenerator.codeplex.com

  41. Tobee says:

    Hi David!

    Thank you for this! But I was just wondering if it's possible to read the contents of the embedded views from other project? 😀