ASP.net (C#) - WebControlAdapter

Source Code

The way ASP.net renders webcontrols don't always produce the desired markup, we might require different markup for different browsers - e.g. a visitor browsing your website from their mobile / cellphone.

We might even feel the need to change the way ASP.net renders webcontrols all together - luckily microsoft provided a mechanism called WebControlAdapters.

For the purpose of this post, imagine the following scenario:

Your employer hands you two lists of options, which he wants selectable from DropDownLists, you think to yourself, awesome I will throw it all together in one DropDownList and distinguish the lists using optgroups - but suddenly it hits you that the standard DropDownList doesn't support optgroups.



After doing some quick research you decide to create a WebControlAdapter as workaround, even though it probably isn't the greatest solution for this issue.

Step 1:
Within the App_Browsers, create a browser file, add the following xml:

 
<browsers>
  <browser refID="Default">
    <controlAdapters>
      <adapter
          controlType="System.Web.UI.WebControls.DropDownList"
          adapterType="CSTruter.DropDownListAdapter" />
    </controlAdapters>
  </browser>
</browsers>
 

Step 2:
Define Group as an attribute within your ListItem(s), something you can also do in the codebehind file, e.g. during databinding.
 
<asp:DropDownList runat="server" ID="ddlA">
	<asp:ListItem Text="C#" Value="1" Group="Microsoft" ></asp:ListItem>
	<asp:ListItem Text="VB.net" Value="2" Group="Microsoft"></asp:ListItem>
	<asp:ListItem Text="PHP" Value="3" Group="Open Source"></asp:ListItem>
	<asp:ListItem Text="Java" Value="4" Group="Open Source" Enabled="false"></asp:ListItem>
	<asp:ListItem Text="Perl" Value="5" Group="Open Source"></asp:ListItem>
</asp:DropDownList>
 

Step 3:
Create a class inheriting from class WebControlAdapter. Override the RenderContents method (RenderBeginTag & RenderEndTag may also be overriden if needed)
 
protected override void RenderContents(System.Web.UI.HtmlTextWriter writer)
{
	// The current control being "adaptered" is available within context from the Control property
	DropDownList dropDownList = (DropDownList)Control;
	ListItemCollection items = dropDownList.Items;
 
	// Retrieve Optgrouping using LinQ
	var groups = (from p in items.OfType<ListItem>()
				  group p by p.Attributes["Group"] into g
				  select new { Label = g.Key, Items = g.ToList<ListItem>() });
 
	foreach (var group in groups)
	{
		if (!String.IsNullOrEmpty(group.Label))
		{
			writer.WriteBeginTag("optgroup");
			writer.WriteAttribute("label", group.Label);
			writer.Write(">");
		}
 
		int count = group.Items.Count();
		if (count > 0)
		{
			bool flag = false;
			for (int i = 0; i < count; i++)
			{
				ListItem item = group.Items[i];
 
				writer.WriteBeginTag("option");
				if (item.Selected)
				{
					if (flag)
					{
						throw new HttpException("Multiple selected items not allowed");
					}
					flag = true;
 
					writer.WriteAttribute("selected", "selected");
				}
 
				if (!item.Enabled)
				{
					writer.WriteAttribute("disabled", "true");
				}
 
				writer.WriteAttribute("value", item.Value, true);
 
				if (this.Page != null)
				{
					this.Page.ClientScript.RegisterForEventValidation(dropDownList.UniqueID, item.Value);
				}
				writer.Write('>');
				HttpUtility.HtmlEncode(item.Text, writer);
				writer.WriteEndTag("option");
				writer.WriteLine();
			}
		}
		if (!String.IsNullOrEmpty(group.Label))
		{
			writer.WriteEndTag("optgroup");
		}
	}
}
 

Consider:
When binding from a datasource, (which you wish to persist using ViewState) you will need to override the adapter ViewState methods in order to persist optgroups, observe:
 
private Object _ViewState;
 
protected override void OnLoad(EventArgs e)
{
    if (Page.IsPostBack)
    {
        if (_ViewState != null)
        {
            Object[] groups = (Object[])_ViewState;
            DropDownList dropDownList = (DropDownList)Control;
            // Add saved optgroups to ListItems
            for (Int32 i = 0; i < groups.Length; i++)
            {
                dropDownList.Items[i].Attributes["Group"] = groups[i].ToString();
            }
        }
    }
    base.OnLoad(e);
}
 
protected override void LoadAdapterViewState(object state)
{
    // Retrieve existing state
    _ViewState = state;
}
 
protected override object SaveAdapterViewState()
{
    DropDownList dropDownList = (DropDownList)Control;
    Int32 count = dropDownList.Items.Count;
    Object[] values = new Object[count];
 
    // Retrieve Optgrouping from ListItem 
    for (int i = 0; i < count; i++)
    {
        values[i] = dropDownList.Items[i].Attributes["Group"];
    }
    return values;
}
 

If all goes according to plan, the WebControlAdapter will override the default markup for all DropDownLists in your solution.






Post comment

Name *
Email
Title
Body *
Security Code
*
* Required fields

Related Posts

Latest Posts

Top 5 posts

Simple WYSIWYG Editor


Creating a WYSIWYG textbox for your website is actually quite simple.
2007-02-01 12:00:00

Moving items between listboxes in ASP.net/PHP example


Move items between two listboxes in ASP.net(C#, VB.NET) and PHP
2008-06-12 17:07:43

Cross Browser Issues: Firefox Word Wrapping


Firefox word wrapping issues
2008-06-09 09:51:21

Populate a TreeView Control C#


Populate a TreeView control in a windows application.
2009-08-27 16:01:03

What time will bring



2007-02-22 12:00:00