Back to Insights

Customize the output of a control with Control Adapters

On a recent project, we had a need to have a DataList render in a very specific way. We needed to have the DataList output two or more columns and for the columns to have an equal number of items in each column (or close to equal if there were an odd number of items). More than this, we needed to have specific CSS classes applied to the columns differently if they are the first column, last column or some other column. We wanted to use the DataList and not just build HTML on the fly so that we could keep the developer experience of databinding to the control and outputting values from the collection of data bound items that our developers and UE developers are used to. We decided to use a Control Adapter to accomplish this.

Control Adapters allow us to override the out-of-the-box rendering of any control and they were new in ASP.NET 2.0. Since a DataList has no way to output items in columns out of the box using a Control Adapter allowed us to use a DataList but to customize the rendering. When a control is rendered on a page, the control base class first checks to see if there is a Control Adapter in place for the control type, if there is it is used for the rendering. To create a Control Adapter, create a new class and inherit from System.Web.UI.WebControls.Adapters.WebControlAdapter and override the Render method. Alternatively you could override the RenderBeginTag, RenderEndTag and the RenderContents methods. You will then have access to the control that is currently being rendered through the Control property of your new Control Adapter.

Here is the control adapter that we created:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.Adapters;

namespace MySite.Web.Controls
{
    public class MenuDataListAdapter : WebControlAdapter
    {
        protected override void Render(HtmlTextWriter writer)
        {
            DataList targetControl = this.Control as DataList;

            if (targetControl == null || targetControl is IRepeatInfoUser == false)
            {
                base.Render(writer);
                return;
            }

            writer.Write("<div class=\"bp-Columns\">");

            IRepeatInfoUser repeaterInfo = (IRepeatInfoUser)this.Control;
            int columns = targetControl.RepeatColumns;
            int items = repeaterInfo.RepeatedItemCount;
            int itemsPerColumn = items / columns;

            for (int i = 1; i <= columns; i++)
            {
                int itemsInThisColumn = itemsPerColumn;

                if ((items % columns != 0) && (i == 1))
                {
                    itemsInThisColumn = (items % columns) + itemsPerColumn;
                }

                writer.Write("<div class=\"bp-Column ");
                if(i == columns)
                {
                    writer.Write("bp-Column-Last");
                }
                writer.Write("\">");

                if ((items % columns != 0) && (i == columns))
                {
                    for (int x = 1; x < itemsInThisColumn + 1; x++)
                    {
                        repeaterInfo.RenderItem(ListItemType.Item, (x + ((i - 1) * itemsPerColumn)), new RepeatInfo(), writer);
                    }
                }
                else
                {
                    for (int x = 0; x < itemsInThisColumn; x++)
                    {
                        repeaterInfo.RenderItem(ListItemType.Item, (x + ((i - 1) * itemsPerColumn)), new RepeatInfo(), writer);
                    }
                }            

                writer.WriteEndTag("div");
            }

            writer.WriteEndTag("div");

        }

    }
}

In order to actually have your website use this Control Adapter, you need to make an entry in your BrowserFile.browser in the App_Browsers folder of your website. If you do not have this folder, you will need to add it. Open the file and in the <controlAdapters> node, add a new adapter element as shown below:

<adapter controlType="MySite.Web.Controls.MyDataList" adapterType="MySite.Web.Controls.MenuDataListAdapter" />

You will notice that for the controlType above, I'm not referencing the out of the box control. This is because I did not want every single DataList on my site to use the custom rendering, I wanted to be able to choose which ones used it. So, I created a simple class that inherits from the DataList control and I use that control whenever I need the custom rendering:

using System;
using System.Collections.Generic;
using System.Text;
using System.Web.UI.WebControls;

namespace MySite.Web.Controls
{
    public class MyDataList : DataList
    {
    }
}

As you can see, the use of Control Adapters allows for some very powerful rendering of out-of-the-box Web Controls when the default rendering does not meet your needs.

Share