Click here to find all the posts relating to the Razor Generator A while back, I blogged about a single file generator I wrote that can precompile Razor helpers. A bit later, Chris van de Steeg started from that code base and extended it to support precompiling MVC views (see his post).
On my side, this project stayed dormant for a little while, but recently we started extending it to support a number of interesting new scenarios, including precompiling MVC views (albeit with a slightly different approach from Chris's).
Most of the new code was written by Pranav Krishnamoorthy, a dev on the ASP.NET team. Many thanks to him for moving this project forward.
Installing the generator
It’s on the VS extension gallery, so install it from there. It’s called “Razor Generator” (not to be confused with “Razor Single File Generator for MVC”, which is Chris’).
Walkthrough to precompile MVC views
You can use it to precompile MVC views either in a separate library or in the MVC project itself. I’ll demonstrate the separate library case, as it’s a bit more interesting.
To begin with, create a new MVC 3 app using Razor (and the ‘Internet Application’ template). Then add a new class library project to the solution (e.g. call it MyPrecompiledViews), and add a reference to it from the MVC project.
Update (4/26/2012): the best approach is to actually create an MVC project for that library, instead of a library project. You'll never actually run it as an Mvc app, but the fact that it comes with the right set of config files allows intellisense and other things to work a lot better than in a library project. See http://razorgenerator.codeplex.com/ for latest info.
Now the fun part begins: using NuGet, install the RazorGenerator.Mvc package into your class library. This adds a number of things to the project:
- A reference to RazorGenerator.Mvc.dll, which contains the view engine
- Logic to register the view engine using WebActivator (in App_Start\PrecompiledMvcViewEngineStart.cs).
- Two web.config files that are there to make intellisense work while you author your views (they’re not used at runtime)
- A sample view, which you can later remove
Let’s take a closer look at that sample view:
Notice that it has a Custom Tool set to RazorGenerator, which causes it to generate a .cs file underneath itself (thanks to the generator we installed earlier).
This is just a sample, so now let’s move the Views\Home\Index.cshtml from the MVC project to the same folder in the class library (you can press Shift during the drag/drop to make it a move). Then set the generator to RazorGenerator as in test.cshtml. You’ll now get an Index.cs nested under Index.cshtml.
And that’s it you’re done! You can now run your app, and it will be using the precompiled version of Home\Index.cshtml.
Why would you want to do that?
One reason to do this is to avoid any runtime hit when your site starts, since there is nothing left to compile at runtime. This can be significant in sites with many views.
Also, you no longer need to deploy the cshtml files at all, resulting in a smaller deployment file set.
Another cool benefit is that it gives you the ability to unit test your views, which has always been something very difficult with the standard runtime compilation model. I’ll cover that in more details in a future post.
Generating files at design time vs. build time
The way the generation works is very similar to T4 templates you have you project. The generation happens as soon as you save the file. You can also force it to regenerate by right clicking on the .cshtml file and choosing Run Custom Tool.
Generally, the guidance is to commit those generated files along with the cshtml file, the same way that you commit all your ‘hand-written’ source files. If you do that, everything will run just fine in an automated build environment.
Another reason to commit the generated files is that it allows you to write code against them with full VS intellisense. e.g. if you use this technique to write Razor helpers that you want to call from other views, you really want VS to know about the generated file at design time. Ditto if you want to write unit tests against your views.
That being said, if you really want to postpone the generation until build time, we’re working on an MsBuild task that will do that. For now, you can find it by getting the RazorGenerator sources on CodePlex.
If you want to help or report issues
This project is hosted on http://razorgenerator.codeplex.com/ under the Ms-PL Open Source license, so feel free to contribute! You can also use CodePlex to discuss and report issues.
Great work David & Pranav!
ReplyDeleteLooks great, but what about the command line scenario? Or is it handled automatically by msbuild?
ReplyDeleteGood question does this work in an automated build environment?
ReplyDeleteThe MsBuild task was added very recently, so it still needs polishing. It does the same things that the Single File Generator (SFG) does, but differs in that unlike the SFG, compiled files are dropped to a temporary directory, and used to generate the binary but not added to the project. This allows you to come up with a workflow where the only files checked in to your source tree are the cshtml files.
ReplyDeleteThe msbuild task only has dependencies on Razor and Mvc assemblies, so you should be able to use it in a CI as any regular msbuild task.
Can we assume this will be added to future releases of MVC as default?
ReplyDelete@kotuadam: right now it's still experimental, but if it picks up and the demand is there, I would think it could find a place in MVC eventually.
ReplyDelete@Jeff & @Brett: I just added a new 'Generating files at design time vs. build time' in the post.
ReplyDeleteGreat news. I am looking forward to get an MSBuild task for that, which could be injected into MSDeploy pipeline.
ReplyDeleteDavid this is a very interesting project I'm going to have to check this out soon. I can't see much value in defering from design time to build time however I'm also a person that commits all my binaries into mercurial.
ReplyDeleteAlso random note there is never a time its correct to make the possesive chris' it will always be chris's, I know this from it being my name. The only time it would be left with the trailing ' is if you were talking about multiple people named chris working on a project which would then be "the chriss' work"
@dotnetchris: thanks for pointing this out! Fixed now :)
ReplyDeletei'm against encouraging modifications to the generated .cs file. With this we might end up having same problems with web forms' code behind?
ReplyDelete@BumbleBee: not quite following you. Nothing here is about encouraging modifications of generated files. It is almost always a bad idea.
ReplyDeleteThis is awesome. Will work great in the Shrink-Wrap apps scenario. But doing it for individual files is too much work. We are awaiting your MSBuild Task version :)
ReplyDeleteCan you tell me, what are the differences compared to Chris's version?
ReplyDeleteI can't find the extension on the extension gallery. I'm guessing it doesn't work with Web Developer right?
ReplyDelete@webdesign: what do you mean by 'doing it for individual files is too much work'? Once a view is set up with the generator, there is nothing special you need to do for it to work. Or are you referring to the act of setting the Custom Tool being painful? We do need to make this easier.
ReplyDelete@fran: one difference is that this uses an MVC View Engine while he uses an alternate implementation. Of course, you can view this as an implementation detail. Also, the use of a nuget package removes a number of set up steps.
ReplyDelete@Sérgio: indeed, the generator won't work on Express. Generally, Express doesn't allow installing VSIX extensions.
ReplyDeleteDon't you just love the fantastic world of .NET where we have to spent loads of time doing all these wonderful things that in all honestly don't directly provide direct customer value but have to be done? Crap like this should be baked into the environment for starters...
ReplyDelete@Gabriel: not sure I understand your comment. Do you mean that you'd like the ability to precompile views to be part of MVC and not an extension. That would be good feedback, but please clarify :)
ReplyDeleteThis is really neat, and I like this functionality, but I think this really ought to be native to the entire MVC system. I appreciate all of the work you guys have done to make this, but this shouldn't be something we need an extension for, in my opinion. With any hope, they will extend MVC 4.0 to allow pre-compiled views out of the box.
ReplyDelete@Stacey: at this point, it's new and experimental. If the feedback is positive and enough people are interested, it's conceivable that it would make its way into the core MVC offering at some point.
ReplyDeleteSeems great.
ReplyDeleteThinking this might be very usefull to use razor views as email templates, if it is possible to easily execute the view and get back the html ?
Will it work with layout/masterpages ?
@Martin: see my following post on unit testing: http://blog.davidebbo.com/2011/06/unit-test-your-mvc-views-using-razor.html
ReplyDeleteWould it be possible to use this to create "pluggable" areas? I'd love it if I could add an area to an MVC app by deploying an assembly. Using the stock functionality, it appears that the views must be under the same Views folder as all other views, which could result in name collisions if I had several assemblies containing precompiled views.
ReplyDelete@Andy: yes, I played with that today and it seems to work well. Check out the sources (from http://razorgenerator.codeplex.com/) for a working sample of an Area defined in a separate assembly
ReplyDeleteThanks.
ReplyDeleteFor some weird reason it is not possible to find T4MVC via Nuget. I can see it on the Nuget wbsite, but when i search via the VS Manager it is not possible to find it.
... or maybe I am the only one with the problem?
@Martin: that seems unrelated to the Razor Generator. But I don't see this behavior. Please follow up on the NuGet forum to keep this post focused :)
ReplyDeleteAnother nice use for your framework is to generate static html of all your views. This is helpful when working with a designer who doesn't use VS. You send the designer your generated output, they tweak it and return to you, you update your Razor code and regenerate. Pretty nice workflow!
ReplyDeleteI did the whole step and no .cs files are being generated.
ReplyDeleteAlso if you do this you have to move your models to the same project as the other views because that'll make a circular reference with typed views
ReplyDelete@Brad: I've never seen a case where nothing gets generated and there is no error. In my experience, when you set the custom tool, either you get an alert that it doesn't know the tool, or it generates the file. If you have a consistent repro, please file a bug on razorgenerator.codeplex.com so we can investigate further.
ReplyDelete@David so apparently I am getting a warning, not an error, that the RazorGenerator cannot be found on this system, so it doesn't seem that NuGet worked.
ReplyDelete@Brad: NuGet will not get you the razor generator. Instead, use the VS extension gallery to get it. See info in the post above :)
ReplyDelete@David already did that, I followed every single step in the whole article, twice ;/
ReplyDeleteStrange, let's take this to codeplex so we can discuss and investigate more properly.
ReplyDeleteThis comment has been removed by the author.
ReplyDeleteI am running on Windows 7 64 bit. I followed all your instructions but RazorGenerator will not run. When I try to run it via "Run Custom Tool" on the cshtml file I get the following error: "Cannot find custom tool 'RazorGenerator' on this system"
ReplyDeleteAny ideas?
@Patricio: strange, it should work. Can you start a discussion thread on http://razorgenerator.codeplex.com/? We'll help you investigate from there. Thanks!
ReplyDeleteDavid, thanks for the quick response.
ReplyDeleteI happened to figure out what the problem was. I added the Razor Generator extension into VS and got confused because the description mentioned "Single File Generator", so I thought I had gotten the wrong one and proceeded to uninstall it. After I uninstalled, I re-installed it. This workflow left some files on my machine, under C:\Users\patricio\AppData\Local\Microsoft\VisualStudio\10.0\Extensions\RazorGenerator contributors\Razor Generator\1.1.1 that had an extension "shoulddelete" or something to that manner. I had to remove them by hand to get the tool to work. Maybe there is some bug in the uninstall logic.
In any case, I am up and testing pre-compiled views.
Thanks again.
Patricio
@Patricio: strange, seems VS got confused. 'Single File Generators' is what those things are called, but I can see it being confusing. Maybe I will change the description. :)
ReplyDeleteHey David.
ReplyDeleteI should wanted to let you know that your and Chris van de Steeg's efforts on this front have made my life, from a re-usability of views across many websites point of a view, really quite a pleasurable experience.
Cheers,
Alex.
@Alex: thanks for the good feedback! :)
ReplyDeleteHi David,
ReplyDeleteThanks for your tutorial. This was just what i'm looking for! I successfully placed my views in the separate class library and MVC can resolve them. But i can't figure out how to override them from my project. I placed A view in /Views/(model)/(viewname).cshtml, but it is not loaded by MVC. Instead, the one from the class library is shown.
This is what i did:
1. Create MVC3 project
2. Create class library
3. Added precompiledmvcviewengine reference to class lib
4. installed razorgenerator
5. set build action of all views to "RazorGenerator"
6. Added references to the web project
7. copied web.config files
What am i missing?
@Simon-Paul: in the App_Start code, try changing the logic so the precompiled engine gets registered into ViewEngines.Engines at the end instead of the beginning. Have not tried this, so let me know how that works for you!
ReplyDelete@David: i added the following function to global.asax.cs and called it in Application_Start():
ReplyDeletepublic static void RegisterViewEngines(ViewEngineCollection viewEngines)
{
viewEngines.Clear();
viewEngines.Add(new RazorViewEngine());
viewEngines.Add(new PrecompiledMvcEngine(typeof(PrecompiledMvcViewEngineStart).Assembly));
}
Works great! Thanks!
@Simon-Paul: glad to hear it! I'm guessing you could skip the first two lines since it should already be in that state by the time you get called. So you just add the new one at the end.
ReplyDelete@Simon-Paul: You could just change the line in PrecompiledViewEngineStart from ViewEngines.Engines.Insert(0, engine) to ViewEngines.Engines.Add(engine). Works for me!
ReplyDelete@David: thanks for this, this is great! I've spent all day trying to figure out how to have my views in a separate assembly before finally stumbling across this. Hope to see it included in a future release of MVC.
@Stewart: thanks for the feedback! Agreed that it would make sense to get this into an MVC release at some point.
ReplyDelete@David,
ReplyDeleteGreat project. I have one issue. When I place my views into a separate project, I lose IntelliSense for any namespaces outside of the core MVC namespaces.
For example, I have MvcMiniProfiler, and I cannot get IntelliSense in my .cshtml files in my class library, even though MvcMiniProfiler has been added to my class library. Even if I add the MvcMiniProfiler namespace in the Views folder web.config file, I still do not get IntelliSense.
If I leave my views as part of my main MVC application, IntelliSense works as expected.
Any advice?
counsellorben
@counsellorben: would you mind starting a discussion about this issue on http://razorgenerator.codeplex.com/? It'll make it easier to discuss than using this post's comments. Thanks!
ReplyDelete@counsellorben: seems like a very similar discussion was just started here: http://razorgenerator.codeplex.com/discussions/268455. So let's use that one.
ReplyDeleteThanks a lot. This is a Great Project!
ReplyDeleteOne question here. I think I can also compile the Layout.cshtml but how would you set a compiled Layout at _ViewStart.cshtml when the Property Layout is expecting a path? Thanks!
You still set the path the same way as it if wasn't precompiled. Generally, you don't need to change the code when you precompile the views. Please follow up at http://razorgenerator.codeplex.com/ if you have further questions, which will make it easier to discuss. Thanks!
ReplyDeleteThanks for this, it's a great project! I love the way controllers just *work* as well from the class library..
ReplyDeleteOne question though - when constructing precompiled views in my class library, it doesn't recognise the @model keyword, so everything comes through as dynamic type. Is there a way to get this to work?
@Steve: this should work. Could you file a bug on http://razorgenerator.codeplex.com/ with more details about what you're trying and what you're seeing? Thanks!
ReplyDeletenice! but...
ReplyDeleteif i have mvc project with _viewStart saying that layout should be "~/Views/Shared/_Layout.cshtml" and im using library with precompiled views. is it possible to override layout in precompiled views?
@max: yes, you should be able to include _viewStart in precompiled libraries and have them behave as normal. If you run into issues, please post to http://razorgenerator.codeplex.com/
ReplyDeleteDavid,
ReplyDeleteTried the steps outlined above, but the installation process only worked partially. Not everything outlined in your list got created.
These are the items that did not get created:
-- Two web.config files that are there to make intellisense work while you author your views (they’re not used at runtime)
-- A sample view, which you can later remove
Tried it seveal times starting from scratch each time (ie. making sure the extension files and the nuGet files were deleted before trying to install them again).
Any suggestions?
Thanks,
Fausto
David,
ReplyDeleteSince the original instructions were not working for me, I decided to skip the "Extension Gallery" portion of the instructions (after having deleted all references to extensions and nuGet packages and dlls), I went ahead an did a direct nuGet for "RazorGenerator". The same result as before (view and web.configs missing). Just for kicks and giggles (and because it has a name similar to the file in the "App_Start" folder) I added "PrecompiledMvcViewEngine" from nuGet, and that automatically added what was missing.
Still not working though, as I get the "Cannot find custom tool 'RazorGenerator' on this system" warning when the "Custom Tool" property is set to "RazorGenerator".
Not sure what it all means (if anything at all), just thought I'd include it for information's sake.
Thanks,
Fausto
@Fausto: could you please start a thread on http://razorgenerator.codeplex.com/ so we can discuss and get to the bottom of it? Thanks!
ReplyDeleteHi David,
ReplyDeleteI'm not able to install Razor Generator on VS 11 Beta, as there is no such extension.. Could you please advise?
@kaatula: yes, it's a known issue. See http://razorgenerator.codeplex.com/workitem/54 for details and updates.
ReplyDelete@David: I downloaded your example to use the views as an external library and works like a charm but when I try to replicate the example on my project it doesn't work. Do I need to do something else besides:
ReplyDelete1. Downloading and installing the nuget pkg (on my views library project which was creating the mvc application template)
2. Setting the CustomTool to RazorGenerator on the view files
3. Referencing on my main site the external views library project
Its worth mentioning that I'm using ASP.NET MVC 4 in this scenario.
@Raul: please use the forum on http://razorgenerator.codeplex.com/ to discuss issues. It tends to work better than blog comments.
ReplyDeleteWill do. Thanks.
DeleteCan precompiling views be done at runtime? I am working on a project where the end user will be able to modify views stored in the database. What I am wondering is if I can have some kind of "publish" process pull them from the database and pre-compile them into the application. Also, it would be helpful to know if it is possible to "publish" views in the production site this way whether the site would have to be taken offline to do so.
ReplyDelete@id: please use http://razorgenerator.codeplex.com/ for all RazorGenerator related discussions. Thanks!
ReplyDeleteHi David,
ReplyDeleteThanks for such a nice solution.
How I can register controller of precompiled into my main application, so that I can access through URL by typing like /controller/action .
Let's I have 'TestController' in precompiled view MVC project with action method say 'sample'.
Intend is to type something like "http://localhost:21685/Test/sample" in URL or set into MapRoute.
Thanks in advance,
Akshay Dubey
@Akshay: please use http://razorgenerator.codeplex.com/ for all RazorGenerator related discussions. Thanks!
ReplyDeleteThis is excellent
ReplyDeleteHI David, thanks for such a great Post. I have gone through many different approaches to achieve plug-gable nature, but yours is the easiest way. I kinda got stuck on one thing, how do I have specific lay out for each dll. As of now each reusable dll is taking the main web application's layout.
ReplyDelete@Pavan: it's best to post all questions to http://razorgenerator.codeplex.com/ instead of here. Thanks!
ReplyDeleteHow to use compiled views by using dll files from razorgenerator tool to another projects.
ReplyDeletePlz help me me me.....
@Rajesh: it's best to post all questions to http://razorgenerator.codeplex.com/ instead of here. Thanks!
ReplyDeleteHi David,
ReplyDeleteThanks, for your quick reply
I have made the separate library by razor generator tool and getting the dll files.
After that i want to access those views inside the dll files to my main mvc projects. I have study carefully this url http://razorgenerator.codeplex.com/. but i can't got the answer.
Note - I am using MVC4 not MVC3.
if possible to send me your Skype id or mail id for doing some chat with you.
Thanks!!
@Rajesh: I didn't mean that you would directly find your answer on the forum, but that it is the right place to post such questions instead of here. Much easier to go back and forth, and others can see it too.
ReplyDelete