Code generation is everywhere these days, from the basic (but getting steadily more complex) Visual Studio IDE generations to full model- and template-based systems (like my preferred option, DeKlarit). Over the years, various methods have evolved to stop code you hand-modified getting overwritten when the code generation runs. These range from comments in the source file warning you not to change anything on pain of overwriting to more complex solutions where you can mark sections of your own code untouchable.
In this post I’m going to explore a way of giving at least some of the same functionality Rails has to ASP.NET 2.0.
Rails Platform Envy
While Ruby on Rails devotees have the scaffolding mechanism available to them, if you use a code generator to give you a head start with ASP.NET applications, you don’t have a good generalised option that’s part of the platform. I’ve been using DeKlarit’s ASP.NET Web Generator for a long time, but when it has come to hand modification I’ve been forced to create alternatively-named files to keep them out of the generator’s way. This is not a great solution; lots of code which is still being generated can need to point to your hand-crafted pages. If you only needed to add a small animated badger in the corner of the screen, you can end up needing to hand-modify everything that points to that one small badger. I like badgers as much as the next man, but this is too much work. We need a way to approximate scaffolding in ASP.NET 2.0.
Platform envy is a great motivator.
Andres Aguiar of DeKlarit sent me a great article about the ASP.NET 2.0
VirtualPathProvider class. This class is really meant for serving content from a filesystem other than the physical filesystem; the link above shows a whole site being served from a ZIP file, but I’m led to believe the existence of the mechanism owes more to SharePoint’s need to serve content from a database. Nonetheless, Andres’s suggestion that we cunningly subvert this mechanism for our own scaffold-related ends interested me greatly.
I’ve since implemented a
BranchedSourcePathProvider that allows any code-generated ASP.NET 2.0 application to use a hand-coding practice which has many of the benefits of Ruby scaffolding – plus one major extra benefit. This was written for use with DeKlarit, but it can be used anywhere where code will be generated in an ASP.NET 2.0 application and you don’t want your hand-coded changes overwritten.
Instead of marking files “untouchable by generation” we create SCM-style “branch maps” where you tell ASP.NET to “prefer” files from a different physical folder if a file has been hand-modified. My use of this approach owes a great deal to Perforce branch specs. To ASP.NET, the site structure appears exactly as normal. Behind the scenes we’re surreptitiously serving up branched files.
For example, if we’ve got a generated file at virtual path
~/GenForm.aspx but we’ve got a hand-modified branched version in
~/Branch/GenForm.aspx, we need a way of telling our
BranchedSourcePathProvider that we want it to use the branched file in preference. To do this, we create a Web.branchmap file in the root of the site our provider will use.
This means that if I request
~/SomeFile.aspx, our provider will first check for the physical existence of
~/Branch/SomeFile.aspx. If it’s there, our provider will return a
Stream to the branched file instead, while the browser client remains blissfully unaware of the branch. Note that it’s a little more complex than this under the hood; in actual fact, ASP.NET will call our provider to determine the existence of the file and any of its dependencies (
GenForm.aspx.cs is an obvious one if you’re using code-behind). There is also one nasty fudge to deal with branched files that still reference UserControls outside of their own branch. I’m not going to go into this here; for the full horror, you’ll be needing the source
What advantage do we get over scaffolding anyway?
In a workflow that involves code generation, I believe this approach is actually a bit more flexible than Ruby scaffolding. This is largely because we’re not actually stopping any generation happening at all – instead, we’re diverting to hand-modified code at run-time only. This means that at any point in time you have both the generated and hand-modified physical files existing simultaneously, so the generated file and any dependencies are always available to act as a donor to your “branch”.
You can get the full source here.