ASP.net (C#) - WebControlAdapter

March 11, 2010 by Christoff Truter C#   ASP.NET  

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++)
            {
                if (groups[i] != null)
                {
                    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.


Related Downloads

ASP.NET webforms WebControlAdapter Demo


Leave a Comment


query March 13, 2013 by Anonymous

dat class in the end wer shud i create it??

August 28, 2012 by vikas

Hi, Thanks a lot,I think this is best working example that i found on net. It solves major problem that comes in final stage of project. Once again thanks and keep it up

October 18, 2011 by Christoff Truter

Hi Rob Thank you for your valuable feedback, I will be sure to add that check in the examples. I will also have a look at your DataSource/DataBind question some time this week and post a solution.

A few tweaks required October 18, 2011 by Rob

Great example - more complete and easy to follow than the one on stackoverflow (http://stackoverflow.com/questions/130020/dropdownlist-control-with-optgroups-for-asp-net-webforms). A change I'd suggest is in the overridden version of OnLoad in Step 3, you need to add a null check before setting the Group attribute value (in case you have provided a listelement in your dropdownlist without the "Group" attribute e.g. a blank 'please select a value' item). i.e. if (groups[i] != null) { dropDownList.Items[i].Attributes["Group"] = groups[i].ToString(); } The final piece of the puzzle I wished you had shown was how to create your DropDownList in the codebehind. The big gotcha was that I couldn't use DataSource/DataBind because when I did I lost the attribute that I had set on the listitems. If you have a way to get DataSource/DataBind working let me know. Thanks though, keep up the good work.

Worked Great October 6, 2011 by Brennan

Hey thanks for this solution. There were some details that you explained here that helped me to get it to work. I couldn't get it to work off of others' examples. It looks like I just had the NameSpace incorrect in the .browser file, thanks for showing the example of it.


    Latest Posts

    Enhance Customer Registration Process with Cutting-Edge Solutions from Card Scanning Solutions Inc.

    November 4, 2013

    JavaScript Threading - Part 2 (Worker)

    December 18, 2012

    JavaScript Threading - Part 1 (Timers)

    December 18, 2012

    MS SQL: Parameter Sniffing

    May 21, 2012

    Solving Cross Browser Issues - Part 3 (Mootools and HTML5)

    February 23, 2012

    Solving Cross Browser Issues - Part 2 (GWT and Dart)

    February 7, 2012

    Solving Cross Browser Issues - Part 1 (JQuery and GWT)

    January 9, 2012

    Be the best stalker you can be

    December 13, 2011

    AjaxControlToolkit (ASP.NET/C#) : CascadingDropDown Extender - Part 2

    November 3, 2011

    AjaxControlToolkit (ASP.NET/C#) : CascadingDropDown Extender - Part 1

    October 7, 2011


    Most Commented on Posts

    Moving items between listboxes in ASP.net/PHP example

    Move items between two listboxes in ASP.net(C#, VB.NET) and PHP
    June 12, 2008

    Simple WYSIWYG Editor

    Creating a WYSIWYG textbox for your website is actually quite simple.
    February 1, 2007

    C# YouTube : Google API

    Post on how to integrate with YouTube using the Google Data API
    March 12, 2011

    Populate a TreeView Control C#

    Populate a TreeView control in a windows application.
    August 27, 2009

    Cross Browser Issues: Firefox Word Wrapping

    Firefox word wrapping issues
    June 9, 2008


    ASP.NET   C#   C++   Comedy   Cross Browser   Design Patterns   IIS   Integration   JavaScript   Microsoft Office   Personal   PHP   SQL   Threading   Visual Basic   XML