2011/09/30

Customizing DataGrid ItemTemplate using ITemplate.InstantiateIn in ASP.NET

I would like to talk in this post about customizing the ItemTemplate in ASP.NET DataGrid. Let's say you have a situation where you want to have a different control in each row in the DataGrid column; for example, the first row you have a textbox in the column, the second row you have a datetime picker in the same column, etc.

The standard implementation of the ItemTemplate in the DataGrid allows you to add one or more controls in each column, and these same controls will be repeated in every row in the grid.

To customize this behavior you have to do the following:
- Create your custom ItemTemplate class, this class should implement the method InstantiateIn in the ITemplate interface.
- Render the controls in each row as required by your implementation in the grid.
The following shows the implementation of this approach:




public class MyCustomItemTemplate : ITemplate
    {
        public Type dataType { get; set; }
        public ListItemType listItemType { get; set; }
        public string columnName { get; set; }
 
        void ITemplate.InstantiateIn(System.Web.UI.Control container)
        {
            switch (listItemType)
            {
                case ListItemType.Header:
                    Label lbl = new Label();
                    lbl.Text = columnName;
                    container.Controls.Add(lbl);
                    break;
                case ListItemType.Item:
                    if (dataType.Equals(typeof(bool)))
                    {
                        CheckBox chk1 = new CheckBox();
                        //chk1.DataBinding += new EventHandler(chk1_DataBinding); // Implement this method to handle your custom binding
                        container.Controls.Add(chk1);
                    }
                    else if (dataType.Equals(typeof(string)))
                    {
                        TextBox txt1 = new TextBox();
                        //txt1.DataBinding += new EventHandler(txt1_DataBinding); // Implement this method to handle your custom binding
                        container.Controls.Add(txt1);
                    }
                    break;
                // Add other data types here and their controls
            }
        }
    }

Now that we have prepared our ItemTemplate implementation, we need to set it inside the grid after the row is created and override the default ItemTemplate rendering; by handling the RowCreated event and calling the InstantiateIn method as follows:


public void GridView1_RowCreated(object sender, GridViewRowEventArgs e)
        {
            if (e.Row.RowType == DataControlRowType.DataRow)
            {               
                // For this example - Odd rows have texboxes and Even ones have checkboxes
                if (e.Row.RowIndex % 2 == 1)
                {
                    GridView gv = (GridView)sender;
                    ITemplate v = ((ITemplate)(((TemplateField)(((DataControlFieldCell)(e.Row.Cells[0])).ContainingField)).ItemTemplate));
                    v = new MyCustomItemTemplate();
                    (v as MyCustomItemTemplate).dataType = typeof(string);
                    (v as MyCustomItemTemplate).columnName = "Item";
                    (v as MyCustomItemTemplate).listItemType = ListItemType.Item;
                    if (((DataControlFieldCell)(e.Row.Cells[0])).Controls.Count > 0)
                    {
                        // This line is to remove default bound controls by the original ItemTemplate (in this case Label control)
                        ((DataControlFieldCell)(e.Row.Cells[0])).Controls.Clear(); // Remove(((DataControlFieldCell)e.Row.Cells[0]).Controls[0]);
                    }
                    v.InstantiateIn((DataControlFieldCell)(e.Row.Cells[0]));
                }
                else
                {
                    GridView gv = (GridView)sender;
                    ITemplate v = ((ITemplate)(((TemplateField)(((DataControlFieldCell)(e.Row.Cells[0])).ContainingField)).ItemTemplate));
                    v = new MyCustomItemTemplate();
                    (v as MyCustomItemTemplate).dataType = typeof(bool);
                    (v as MyCustomItemTemplate).columnName = "Item";
                    (v as MyCustomItemTemplate).listItemType = ListItemType.Item;
                    if (((DataControlFieldCell)(e.Row.Cells[0])).Controls.Count > 0)
                    {
                        // This line is to remove default bound controls by the original ItemTemplate (in this case Label control)
                        ((DataControlFieldCell)(e.Row.Cells[0])).Controls.Clear(); //Remove(((DataControlFieldCell)e.Row.Cells[0]).Controls[0]);
                    }
                    v.InstantiateIn((DataControlFieldCell)(e.Row.Cells[0]));
                }
            }
        }
    }

Of course, in the aspx page you would have something like this:


 <asp:GridView ID="GridView1" runat="server" OnRowCreated="GridView1_RowCreated" AutoGenerateColumns="false">
        <Columns>            
            <asp:TemplateField HeaderText="Custom Item Template">
                <ItemTemplate>
                    <asp:Label ID="lblDesc" Text='<%# Bind("Desc") %>' runat="server"></asp:Label>
                </ItemTemplate>
            </asp:TemplateField>
        </Columns>
    </asp:GridView>

With this implementation, we have a datagrid that renders different controls for each row according to our requirements.

2011/02/04

WCF Serialization: Dynamically add derived types to web service ServiceKnownType attribute

It is a known issue that you need to add the list of derived types of your base class in order for the WCF serializer to recognize them.

Suppose you have a class hierarchy like the following:

public abstract class MyBaseClass
{
[DataMember]

public int id { getset; }

}

public class DerivedType : MyBaseClass
{
[DataMember]
public string Name { getset; }

}

Now in your WCF service definition, your code should like this:

[ServiceContract]
interface MyService
{
[OperationContract]
[ServiceKnownType(typeof(DerivedType)]
void HelloWorld(MyBaseClass param1);

}

This obviously creates a problem that anytime you add a new derived type; you need to add it to the ServiceKnownType attribute so that the WCF serializer recognizes the new types.

To solve this problem and allow the newly created derived types to be dynamically serialized without the need to modify the service definition code, you need to do the following:

Create a method that returns a list of the derived types as follows:

public static class TypesHelper
{
public static IEnumerable GetKnownTypes(ICustomAttributeProvider provider)
{

Assembly assembly = Assembly.GetAssembly(typeof(MyBaseClass));
List<Type> types = assembly.GetExportedTypes().Where(e => e.BaseType != null && e.BaseType.BaseType == typeof(MyBaseClass)).ToList();

return types;

}

}

Now add a call to the above created method in the ServiceKnownType attribute as follows:

[OperationContract]
[ServiceKnownType("GetKnownTypes", typeof(TypesHelper))]
void HelloWorld(MyBaseClass param1);

This way, we are benefiting from the ability to use our own method that dynamically supplies the derived types to the WCF serializer.

2011/01/30

Client side validation of a control defined inside a web user control in ASP.NET

I came across the issue of validating user input using ASP.NET client-side validators, the requirement was to validate the whole page controls with client-side validation; including controls (textboxes, dropdowns, etc) that are defined inside web user controls.

Apparently, the ASP.NET client validators that are defined in the page but outside the user controls will not recognize the control ID inside the user control; so the solution was to point to the correct ID of that control in the client validator using the ControlToValidate property.

In this case the ID of the control will be: UserControlID$ControlID.

The definition of the client side validation will be as follows:

<asp:RequiredFieldValidator ID="RequiredFieldValidator1" runat="server" ValidationGroup="ValidationGroup1" ControlToValidate="UserControlID1$TextBox1"></asp:RequiredFieldValidator>

This also works with the ValidationGroup property as well, provided that the ValidationGroup property for the control(s) inside the user control are set. This can be accomplished through a set property defined for the user control that will set its child controls accordingly.