The Ultimate ASP.NET Project
I’ve always liked the simplicity of Web Sites (as opposed to Web Application projects). There’s no need to compile or publish, there’s no project file, you can freely edit and deploy individual pages just like you would with an ordinary HTML file. And since Razor doesn’t use codebehind files, Web Sites became even more convenient. Of course, medium to large sites depend on code that don’t belong in pages, but for that I’d create a class library and reference it from the Web Site.
Downsides of Web Sites
The larger your site becomes and the more code it uses, then Web Sites become less attractive and more painful to maintain, specially for deployment.
For example, if you do refactoring of shared code that affects many pages, then manually deploying each page is tedious and error prone, e.g. you could forget to upload a page. There’s no way to know if your production site compiles entirely without issues, since each directory is compiled on demand. Every time I’d do a big deployment I’d do a manual test of the site and prayed for no errors on the Event Viewer.
The bottom line is, runtime compilation is not a good idea. What’s the point of using a sophisticated IDE like Visual Studio if you’ll end up deploying source code?
ASP.NET Precompilation
Microsoft’s solution to this problem is called Precompiled Web Sites, and it’s an awful solution.
Precompiled sites are created using aspnet_compiler.exe
, which does the same thing the ASP.NET runtime does to compile your site, except instead of outputting the assemblies to the ASP.NET temporary folder, you get them on your site’s Bin folder. Remember, one assembly per directory. If you have a lot of directories you’ll end up with a lot of assemblies, with cryptic names like App_Web_t0r3kxzp.dll
.
Imagine you update a single page, you’d have no idea which assembly contains your changes. When you precompile you have to re-deploy your entire site. This could mean having to bring down your site to completely upload it, or use a specialized tool like Web Deploy, but I’m not interested in more tools to solve the problems created by other tools (that’s an addict’s pattern).
Another big downside of precompiled sites is that you still have to deploy your pages! The reason is that the page is responsible for routing a request to the appropriate compiled page class (or in more technical words, triggering the page handler factory). In fact, all the content from the page is removed and replaced with: “This is a marker file generated by the precompilation tool, and should not be deleted!”. Routing is a problem that precompiled sites don’t solve.
With ASP.NET MVC the idea of a precompiled site is a lot simpler: MVC projects use explicit routing; controllers are just classes; there are tools to precompile your views; the view engine abstraction can be used to load precompiled views. No need for aspnet_compiler.exe
.
What’s missing
To have a better precompiled sites solution we need two things: a tool to generate code for pages that is not tied to the ASP.NET runtime, and a way to route without using marker files. The first depends on the page format you use (WebForms, Razor, etc.) and this post is format agnostic. For the second problem, there’s an existing solution that works really well.
ASP.NET Web Pages have always supported extensionless URLs. An IHttpModule is used to remap a request when a page for a particular URL exists (you can find a simplified version of this module here). The same technique can be used to remap the request but to a precompiled page class.
A better solution
My ideal ASP.NET project is not one project but multiple projects: one Web Site and several class libraries. For example:
com.example/ ├── Bin/ ├── css/ ├── img/ ├── js/ ├── Web.config com.example.catalog/ ├── index.page ├── search.page ├── ... ├── com.example.catalog.csproj com.example.checkout/ ├── checkout/ │ ├── shipping.page │ ├── billing.page │ ├── confirm.page ├── cart.page ├── ... ├── com.example.checkout.csproj
The Web Site only contains static files (css, js, img) and binaries. The Web Site has no pages.
Each class library project represents a section (or module) of the site. In a pre-build event, the code for all the pages in the project is generated, and then compiled into the class library. Each generated page class is annotated (using an attribute) with the application relative path of the page. This info will be used at run-time to map a URL to a specific page class.
The Web Site references each class library. When the solution is built, the Web Site becomes a precompiled site. No need for a publish step. And since the Web Site itself has no code, it doesn’t need to reference the DotNetCompilerPlatform package.
When you split your precompiled site into several projects, you can update and deploy sections of your site without having to redeploy everything. No marker files, no .compiled
files, only assemblies. That’s a huge win.