I think many asp.net developer looking at the markup generated by a ASP.NET pages wanted to get rid of those white space without necessity of changing nice indents of their pages and user controls.
Existing solutions of the problem based on one of the two methods: implementing a cleaning HtmlTextWriter and returning an instance of it in Page.CreateHtmlTextWriter override, or implementing a cleaning Stream and setting the HttpResponse.Filter property to an instance of the Stream.
But both methods incur runtime performance costs (I am going to show in a next post why that happens).
In my solution white space in generated markup removed at pages’ compile time, so that at runtime it looks as if white space was removed manually by the developers.
One would say that it is not actually a solution because it relies on Reflection and depends on implementation details of the particular version of ASP.NET. I would answer – Thanks God that I haven’t had to resort to the help of unsafe code and pointer arithmetic :)
Let’s start with the beginning. When ASP.NET generates C#/VB code for an ASP.NET page it uses ControlBuilders representing almost every entity (except text) in the page tree. Besides holding information about the part of the page it represents, they let controls creators change different aspects of generated code (in .NET 3.5 SP1 including ability to change generated CodeDom). For regular pages control builders exist at compile time only, but in “no compile” pages they actually create at runtime the parts they are responsible for.
As a page at runtime time is a tree of controls, server side code and text , the page at compile time is a tree of control builders. Each builder holds a collection of subbuilders in an ArrayList. Untyped collection is used because literal strings are placed in subbuilders collection not being wrapped in control builders.
Knowing all that we can traverse control builder tree from the root, iterate over the each builder’s subbuilders collection, find instances of strings and replace them with cleaned versions.
Getting access to the root control builder at right time was not easy. I tried different ways but all had shortcomings or inconveniences. But then examining .NET sources by Reflector I found the perfect way - PageParserFilter. PageParserFilter “provides an abstract base class for a page parser filter that is used by the ASP.NET parser to determine whether an item is allowed in the page at parse time”. This ability (among other useful things) was added to ASP.NET 2.0 as a result of cooperation between ASP.NET team and SharePoint team poring their product to ASP.NET. The only method of interest now is PageParserFilter.ParseComplete(ControlBuilder rootBuilder) and it is the right place and time for accessing the root builder. The remaining part is easy. See the attached web site project. Compile the classes in a dll, drop it in Bin folder, small change to web.config and voilà– ASP.NET white space cleaning with no runtime cost!
BTW the code has been working for a few projects in the wild with no problems.
UPDATE (17 Aug 2010): I’ve updated the sample to work with MVC 2 and MVC 3 Preview 1 (including support of Razor view engine).
UPDATE (9 Oct 2010): Now it works with MVC 3 Beta.
UPDATE (4 May 2011): Finally with MVC 3 RTW