вторник, 29 сентября 2009 г.

ASP.NET white space cleaning with no runtime cost

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

33 комментария:

  1. 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).

    ОтветитьУдалить
  2. @thorn: Yes, it seems ControlBuilder mechanism is not that flexible bacause it was created mostly for internal purposes

    ОтветитьУдалить
  3. Let's vote for making this mechanism more open: http://aspnet.uservoice.com/forums/41202-asp-net-webforms/suggestions/488607

    ОтветитьУдалить
  4. 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.

    ОтветитьУдалить
  5. @Flori: Have you set debug="false" in your web.config?

    ОтветитьУдалить
  6. 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

    ОтветитьУдалить
  7. 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!

    ОтветитьУдалить
  8. What about css and js optimization? Is this posible using this technology?

    ОтветитьУдалить
  9. Kiril, unlike web pages, css and js are static in their nature. They can be easily minimized, combined, zipped and cached.

    ОтветитьУдалить
  10. 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! :)

    ОтветитьУдалить
  11. 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

    ОтветитьУдалить
  12. Anybody have a working version with ASP.NET MVC 3 RTM ?

    ОтветитьУдалить
  13. Hey, great tool! Any chance of an update since the the latest MVC release?

    ОтветитьУдалить
  14. Very impressive... Was looking for exactly this! Thank you very much!

    ОтветитьУдалить
  15. Does it work with AJAX?

    ОтветитьУдалить
  16. Why do you require debug="true"?

    May I step over this check in you code just to debug it?

    ОтветитьУдалить
  17. 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.

    ОтветитьУдалить
  18. 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.

    ОтветитьУдалить
  19. 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.

    ОтветитьУдалить
  20. This is really cool. Thanks for building it!!

    Please consider making this into a Nuget package so it can be easily installed and discovered.

    ОтветитьУдалить
  21. 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.

    ОтветитьУдалить
  22. 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?

    ОтветитьУдалить
  23. 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.

    ОтветитьУдалить
  24. What about version for MVC4?
    The previous versions works great.

    ОтветитьУдалить
  25. Этот комментарий был удален автором.

    ОтветитьУдалить
  26. Nice work. unfortunately, This is not working at all for me in MVC3.
    Can you upload a working solution.It would helpfil if you have a readme.txt file to demonstrate what steps need to follow to make it work.

    ОтветитьУдалить
  27. Great Solution. It helped me a lot.:)

    ОтветитьУдалить
  28. I can see attached solution. Where can I download the code from?

    ОтветитьУдалить