Add the following code to your base page and base user control.
protected TItem Eval<TItem>()
{
return (TItem)GetDataItem();
}
Add the following code to your base page and base user control.
protected TItem Eval<TItem>()
{
return (TItem)GetDataItem();
}
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
There is new type in .NET BCL - Tuple. What is it is explained in the article CLR Inside Out: Building Tuple. The BCL team has decided it to be a reference type. The reason is well motivated but I think there are cases when having a tuple as a valuetype is beneficial. One of the such case is when you use a tuple as a key in a dictionary. Creating an object in the heap just to get a value from the dictionary or to find out whether it exists would be.. at least not proper use of GC. Especialy when you do it in a tight loop.
Honestly, using keys in dictionaries were the only reason I have used tuples before. Now not to interfere with with Tuple from BCL and to conform to it I have renamed my value type Tuple to TupleValue, properties Value1, Value to Item1, Item2, factory method New to Create.
Implementation is trivial. The only important detail is IEquatable<T>. A value type not implementing this interface is boxed when used as a key in a dictionary.
using System;
using System.Collections.Generic;
namespace Omari
{
public static class TupleValue
{
public static TupleValue<TItem1, TItem2> Create<TItem1, TItem2>(TItem1 item1, TItem2 item2)
{
return new TupleValue<TItem1, TItem2>(item1, item2);
}
public static TupleValue<TItem1, TItem2, TItem3> Create<TItem1, TItem2, TItem3>(TItem1 item1, TItem2 item2, TItem3 item3)
{
return new TupleValue<TItem1, TItem2, TItem3>(item1, item2, item3);
}
}
public struct TupleValue<TItem1, TItem2> : IEquatable<TupleValue<TItem1, TItem2>>
{
TItem1 _item1;
TItem2 _item2;
public TItem1 Item1 { get { return _item1; } }
public TItem2 Item2 { get { return _item2; } }
public TupleValue(TItem1 item1, TItem2 item2)
{
_item1 = item1;
_item2 = item2;
}
public bool Equals(TupleValue<TItem1, TItem2> other)
{
return EqualityComparer<TItem1>.Default.Equals(_item1, other.Item1) &&
EqualityComparer<TItem2>.Default.Equals(_item2, other.Item2);
}
public override bool Equals(object obj)
{
if (obj.GetType() != typeof(TupleValue<TItem1, TItem2>))
return false;
return Equals((TupleValue<TItem1, TItem2>)obj);
}
public override int GetHashCode()
{
return EqualityComparer<TItem1>.Default.GetHashCode(_item1) ^
EqualityComparer<TItem2>.Default.GetHashCode(_item2);
}
}
public struct TupleValue<TItem1, TItem2, TItem3> : IEquatable<TupleValue<TItem1, TItem2, TItem3>>
{
TItem1 _item1;
TItem2 _item2;
TItem3 _item3;
public TItem1 Item1 { get { return _item1; } }
public TItem2 Item2 { get { return _item2; } }
public TItem3 Item3 { get { return _item3; } }
public TupleValue(TItem1 item1, TItem2 item2, TItem3 item3)
{
_item1 = item1;
_item2 = item2;
_item3 = item3;
}
public bool Equals(TupleValue<TItem1, TItem2, TItem3> other)
{
return EqualityComparer<TItem1>.Default.Equals(_item1, other.Item1) &&
EqualityComparer<TItem2>.Default.Equals(_item2, other.Item2) &&
EqualityComparer<TItem3>.Default.Equals(_item3, other.Item3);
}
public override bool Equals(object obj)
{
if (obj.GetType() != typeof(TupleValue<TItem1, TItem2, TItem3>))
return false;
return Equals((TupleValue<TItem1, TItem2, TItem3>)obj);
}
public override int GetHashCode()
{
return EqualityComparer<TItem1>.Default.GetHashCode(_item1) ^
EqualityComparer<TItem2>.Default.GetHashCode(_item2) ^
EqualityComparer<TItem3>.Default.GetHashCode(_item3);
}
}
}