I have a fairly big asp.net website that use GridView bound to the same object in lots of places. I'm using an item template to customize each row. However to have the same template in all the pages I have to copy & paste the item template to each page. Obviously this is not the best solution. On top of this I want to be able to change the template used by the GridView, by changing some configuration file. One option would be to create an user control with the DataGrid and expose the necessary properties to be used in each page. However this does not fulfill the second requirement to be able do dynamically change the template. Basically I'm looking for a way to say to the GridView to use a template and be able to do this dynamically. Any idea would be helpful.
In order to accomplish what you want, you have two options as I see it:
1.) Build each TemplateField dynamically in code, and switch these based on some configuration.
2.) Create user controls for your custom grids and use those instead.
I know you said you don't want to use a UserControl because that will take away your ability to dynamically change your layout, but let me challenge that presupposition with an example.
You can make use of built-in ASP.Net features in order to dynamically switch out user controls to your liking by using a PlaceHolder Control.
<asp:PlaceHolder ID="GridViewPlaceHolder" runat="server" />
Your custom grids can be built declaratively in .ascx files and then loaded into place dynamically at runtime like so:
GridViewPlaceHolder.Controls.Add(LoadControl("~/Controls/MyCustomControl.ascx"));
Now, if you really want to make your life easier, then you can create an abstract base class that all your custom grid controls will inherit from. In this way, your controls can be treated generically when loaded.
public abstract class CustomGridControl: System.Web.UI.UserControl
{
public abstract Object DataSource { get; set; }
}
A simple grid can be defined in markup:
<asp:GridView ID="myGridView" runat="server" AutoGenerateColumns="false">
<Columns>
<asp:TemplateField HeaderText="Name">
<ItemTemplate>
<asp:Label Text='<%#Eval("Name") %>' runat="server"></asp:Label>
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="Age">
<ItemTemplate>
<asp:Label Text='<%#Eval("Age") %>' runat="server"></asp:Label>
</ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
And your code behind for that control would look something like this:
public partial class SimpleGrid : CustomGridControl
{
public override object DataSource
{
get { return myGridView.DataSource; }
set { myGridView.DataSource = value; }
}
}
Now the page or control that utilizes this only has to cast to the base class, and you can use it generically. The following is a simple example of how you might use this, but I think it makes the point clearly:
protected void Page_Load(object sender, EventArgs e)
{
var dataSource = new List<MyCustomClass>
{
new MyCustomClass{Name = "Josh", Age = 43},
new MyCustomClass{Name = "Bob", Age = 14},
new MyCustomClass{Name = "Ashley", Age = 32},
};
DynamicallyLoadUserControlGrid("~/GridViewTemplates/SimpleGrid.ascx", dataSource);
}
private void DynamicallyLoadUserControlGrid(String controlLocation, List<MyCustomClass> dataSource)
{
var ctrl = (CustomGridControl)LoadControl(controlLocation);
ctrl.DataSource = dataSource;
ctrl.DataBind();
GridViewPlaceHolder.Controls.Add(ctrl);
}
So, there you have it. Custom templated controls without all the nasty headache of trying to build them all up manually in code. I am going to post the completely manual way of doing this in another answer, but once you see it, I think you will agree that this method is preferred.
Alright, so here is the 100% manually building up of templated fields example.
The first step in creating dynamic template columns is to create a class that implements the System.Web.UI.ITemplate interface. For our simple example here, I am just going to use a label.
public class MyCustomTemplate : ITemplate
{
public String DataField { get; set; }
public MyCustomTemplate(String dataField)
{
DataField = dataField;
}
public void InstantiateIn(Control container)
{
var label = new Label();
label.DataBinding += label_DataBinding;
container.Controls.Add(label);
}
void label_DataBinding(object sender, EventArgs e)
{
var label = (Label)sender;
var context = DataBinder.GetDataItem(label.NamingContainer);
label.Text = DataBinder.Eval(context, DataField).ToString();
}
}
Notice that in order to support DataBinding, you will have to manually handle that event on whatever control(s) you choose to add. Once you have developed your template, you can then use this as your ItemTemplate for any TemplateField that you want to use.
So, suppose that we have some collection of custom business objects that we want to bind our grid to.
public class MyCustomClass
{
public String Name { get; set; }
public Int32 Age { get; set; }
}
We are going to have to manually build up each column as a TemplateField, and then bind our collection to the GridView. In order to make this cleaner and easier to do, I have encapsulated the building up of the TemplateField collection into a static helper class:
public static class MyCustomTemplateCollection
{
public static DataControlFieldCollection GetTemplateCollection()
{
var col = new DataControlFieldCollection();
var nameField = new TemplateField
{
HeaderText = "Name",
ItemTemplate = new MyCustomTemplate("Name")
};
var ageField = new TemplateField
{
HeaderText = "Age",
ItemTemplate = new MyCustomTemplate("Age")
};
col.Add(nameField);
col.Add(ageField);
return col;
}
}
Using this in your code behind would look something like this:
protected void Page_Load(object sender, EventArgs e)
{
var dataSource = new List<MyCustomClass>
{
new MyCustomClass{Name = "Josh", Age = 43},
new MyCustomClass{Name = "Bob", Age = 14},
new MyCustomClass{Name = "Ashley", Age = 32},
};
DynamicGrid(dataSource);
}
private void DynamicGrid(List<MyCustomClass> dataSource)
{
var col = MyCustomTemplateCollection.GetTemplateCollection();
foreach (DataControlField field in col)
{
myGridView.Columns.Add(field);
}
myGridView.DataSource = dataSource;
myGridView.DataBind();
}
In the end, this will produce an output that is identical to the dynamic user controls example, but as you can see, it is much more cumbersome. This is also a very simplistic example that doesn't include any CSS attributes, or multiple types of controls. You could do this, but it would be slit-your-wrists-painful, and might make you want to quit programming all together. Go with the user control solution and make your life easier.