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
Wow! Thanks. I've been looking for this solution for a long time. Even the excellent 'Ultra-Fast ASP.NET' book doesn't cover this topic (tricks of the compile-time stage).
ОтветитьУдалить@thorn: Yes, it seems ControlBuilder mechanism is not that flexible bacause it was created mostly for internal purposes
ОтветитьУдалитьLet's vote for making this mechanism more open: http://aspnet.uservoice.com/forums/41202-asp-net-webforms/suggestions/488607
ОтветитьУдалитьhow do you make this work? I've changed the web.config, added the classes and pasted using Omari.Web.UI in my page and masterpage. It does noghing yet.
ОтветитьУдалить@Flori: Have you set debug="false" in your web.config?
ОтветитьУдалитьThis worked like a charm for me. Basically you just need the three class files in the app_Code and build in the project. My page size went form 142kb to 90kb ~50kb reduction.
ОтветитьУдалитьCheckout my live implementation at http://fullmeals.net
this is EXCELLENT. I hope you package it up somehow and sell it, honestly. Make a few small improvements to cleaning up additional whitespace and it will be golden. Well done, great work.
ОтветитьУдалитьewart @ ihug dot co dot nz
wish I knew which button was send in russian!
Thanks, ewart.
ОтветитьУдалитьWhat about css and js optimization? Is this posible using this technology?
ОтветитьУдалитьKiril, unlike web pages, css and js are static in their nature. They can be easily minimized, combined, zipped and cached.
ОтветитьУдалитьHi Omari,
ОтветитьУдалитьThis project sounds great and I'd really like to try it out, but I'm having trouble getting this to work even for the included WebForms or the MMVC2 example projects (I'm not using MVC3 Beta yet, although I'm looking forward to Razor when it goes RTM). I double checked that debug="false" is set and the binaries are where they are supposed to be, I've tried running it from Visual Studio 2010 and from IIS 7.5, and I've even tried re-downloading and extracting the zip somewhere else and starting from scratch again to get one of the examples to work. But no luck. Am I missing something obvious? Is it possible that the recent MVC3 update broke MVC2 support?
Looking forward to getting it working! :)
Any chance you can update again to include proper support in RC2? I had an issue here http://stackoverflow.com/questions/4414231/why-am-i-getting-asp-net-mvc3-razor-syntax-errors
ОтветитьУдалитьAnybody have a working version with ASP.NET MVC 3 RTM ?
ОтветитьУдалитьIs it compatible with MVC3 and razor?
ОтветитьУдалитьHey, great tool! Any chance of an update since the the latest MVC release?
ОтветитьУдалитьCool, thanks for sharing
ОтветитьУдалитьВаши усилия ценятся!
ОтветитьУдалитьVery impressive... Was looking for exactly this! Thank you very much!
ОтветитьУдалитьDoes it work with AJAX?
ОтветитьУдалитьWhy do you require debug="true"?
ОтветитьУдалитьMay I step over this check in you code just to debug it?
Can't find attachment.
ОтветитьУдалитьwww.gourmandia.com - Gourmandia is a culinary website offering videos of world-class Michelin rated chefs exhibiting their techniques. Also features documentaries on fine dining restaurant locations and cities, recipes, forum, and more. At www.gourmetrecipe.com - Gourmet Recipe is the place to find the tastiest, healthiest gourmet recipes. Watch videos of great chefs preparing meals, find easy beginner dishes, and more.
ОтветитьУдалитьThere are many ways to improve your cooking abilities into the whole new level. Try to visit these sites http://www.gourmandia.com , http://www.gourmetrecipe.com , http://www.foodiamond.com , http://www.xdestination.com and their sub-domains. Feel free to suggest and recommend what these sites can offer. Online cooking recipes are never been this good for they have featured recipes that are truly remarkable. In addition, the site offers easy and quick recipes with featured video that are easy to follow from the world-class Michelin. Visit now and have some fun.
ОтветитьУдалитьI appreciate your time and effort to come up this kind of article. Try to take a glance at gourmetrecipe. A recommendation for you to relax and unwind for a while...In that website you will find many tasty and nutritious dishes for you to choose from and your family. I also assure you, you won't regret it for visiting this site.
ОтветитьУдалитьThis is really cool. Thanks for building it!!
ОтветитьУдалитьPlease consider making this into a Nuget package so it can be easily installed and discovered.
Such a wonderful post. Asp.net Development is one of the most popular application development now-a-days. We are also providing some good tips related to this post.Great work.
ОтветитьУдалитьThis is the best solution I've seen. I tried recompiling for MVC4 but it fails to load the WhiteSpaceCleaningMvcWebRazorHostFactory. Any pointers on what I might need to do in order for it to work?
ОтветитьУдалитьVery cool post. Nuget package would be a great idea.
ОтветитьУдалитьHowever, It doesn't seem to work with MVC3. I'm getting "Access is denied: 'System.Web.Mvc.Razor.MvcCSharpRazorCodeGenerator'." errors. Is there a workaround to fix it?
Thanks.
What about version for MVC4?
ОтветитьУдалитьThe previous versions works great.
Этот комментарий был удален автором.
ОтветитьУдалить