Introduction
Typically when I think of caching I think of reusing data that has been extracted from a datasource; however, you can cache any type of object in ASP.NET and a dynamically generated FormView Template is a great object to use caching with.
I have an application that allows administrators of the application to add fields to it at runtime. Which means the forms for those fields need to be created at runtime also. The FormView allows you to dynamically generate templates at runtime using the IBindableTemplate; however, it seems really inefficient to dynamically generate the form when nothing has changed.
Using the Code
So here is an example of a FormView readonly item template. The logic is a little complicated because my layout is based on data types and what not. The point is that you can do this with any template layout.
public partial class ItemTemplate : IBindableTemplate
{
Constants constants = new Constants();
TextModification textModification = new TextModification();
LinqData linqData = new LinqData();
DataTable GetColumnInformation = new DataTable();
TaskDataSet tds = new TaskDataSet();
private string _tableName;
public string tableName
{
get { return _tableName; }
set
{
_tableName = value;
GetColumnInformation = linqData.GetColumnsForTable(_tableName);
}
}
void ITemplate.InstantiateIn(Control container)
{
Panel myPanel = new Panel();
DataTable table = new DataTable();
table = GetColumnInformation;
Table itemTable = new Table();
itemTable.CssClass = "formView";
int index = 0;
int rowIndex = 0;
bool needNewRow = false;
ArrayList textColumnNames = new ArrayList();
ArrayList textColumnDisplayNames = new ArrayList();
bool needTabPanel = false;
foreach (DataRow col in table.Rows)
{
TableCell newCell = new TableCell();
newCell.Width = Unit.Percentage(20);
string columnName = col.ItemArray.GetValue(0).ToString();
string colDataType = col.ItemArray.GetValue(1).ToString();
string colDetails = col.ItemArray.GetValue(2).ToString();
string columnDisplayName = col.ItemArray.GetValue(3).ToString();
bool textColumn = colDetails.ToLower().Contains("text");
TableRow firstRow;
if (constants.showItemMode(columnName, tableName))
{
if ((index & 1) == 0 || needNewRow == true ||
textColumn || columnName == "created_by")
{
firstRow = new TableRow();
itemTable.Rows.Add(firstRow);
needNewRow = false;
if (index != 0)
{
rowIndex++;
}
else
{
needNewRow = true;
}
index = 2;
}
else
{
firstRow = itemTable.Rows[rowIndex];
}
if (textColumn)
{
textColumnDisplayNames.Add(columnDisplayName);
textColumnNames.Add(columnName);
needTabPanel = true;
}
else
{
newCell.Text = columnDisplayName;
firstRow.Cells.Add(newCell);
Label firstNameLabel = new Label();
if (columnName.Contains("_id") && columnName != tableName + "_id")
{
firstNameLabel.ID = columnName.Replace("_id", "_name") + "Label";
}
else
{
firstNameLabel.ID = columnName + "Label";
}
firstNameLabel.DataBinding += new EventHandler(Label_DataBinding);
TableCell anotherNewCell = new TableCell();
anotherNewCell.Controls.Add(firstNameLabel);
firstRow.Cells.Add(anotherNewCell);
}
index++;
}
}
TableRow newRow = new TableRow();
itemTable.Controls.Add(newRow);
TableCell tableCell = new TableCell();
tableCell.ColumnSpan = 4;
EditButton updateButton = new EditButton();
updateButton.ID = "defaultButton";
tableCell.Controls.Add(updateButton);
newRow.Cells.Add(tableCell);
myPanel.Controls.Add(itemTable);
myPanel.DefaultButton = "defaultButton";
myPanel.Controls.Add(new LiteralControl("<br />"));
if (needTabPanel == true)
{
myPanel.Controls.Add(CreateTabPanelForNotes(tableName,
textColumnNames, textColumnDisplayNames));
}
container.Controls.Add(myPanel);
}
public TabContainer CreateTabPanelForNotes(string tableName,
ArrayList textColumnNames, ArrayList textColumnDisplayNames)
{
TabContainer myTabContainer = new TabContainer();
myTabContainer.ID = tableName + "TabContainer";
for (int i = 0; i < textColumnDisplayNames.Count; i++)
{
TabPanel myTabPanel = new TabPanel();
myTabPanel.ID = textColumnNames[i].ToString() + "TabPanel";
myTabPanel.HeaderText = textColumnDisplayNames[i].ToString();
TextBox firstNameLabel = new TextBox();
firstNameLabel.ID = textColumnNames[i].ToString() + "TextBox";
firstNameLabel.TextMode = TextBoxMode.MultiLine;
firstNameLabel.Rows = 25;
firstNameLabel.Width = Unit.Percentage(100);
firstNameLabel.ReadOnly = true;
firstNameLabel.CssClass = "readOnlyTextBox";
firstNameLabel.DataBinding += new EventHandler(TextBox_DataBinding);
myTabPanel.Controls.Add(firstNameLabel);
myTabContainer.Tabs.Add(myTabPanel);
myTabContainer.TabIndex = 0;
}
return myTabContainer;
}
private void Label_DataBinding(Object sender, EventArgs e)
{
Label firstNameLabelControl = (Label)sender;
FormView formViewContainer = (FormView)firstNameLabelControl.NamingContainer;
string columnName = firstNameLabelControl.ID.Replace("Label", "");
string addLineBreaksToText = "";
addLineBreaksToText = linqData.GetDataForFormViewBoundControl(formViewContainer,
columnName).ToString().Replace(Environment.NewLine, "<br />");
firstNameLabelControl.Text = addLineBreaksToText;
}
private void TextBox_DataBinding(Object sender, EventArgs e)
{
TextBox firstNameLabelControl = (TextBox)sender;
FormView formViewContainer = null;
if (firstNameLabelControl.NamingContainer.ToString() ==
"System.Web.UI.WebControls.FormView")
{
formViewContainer = (FormView)firstNameLabelControl.NamingContainer;
}
else
{
formViewContainer = (FormView)
firstNameLabelControl.NamingContainer.NamingContainer.NamingContainer;
}
string columnName = firstNameLabelControl.ID.Replace("TextBox", "");
string addLineBreaksToText = "";
addLineBreaksToText = linqData.GetDataForFormViewBoundControl(
formViewContainer, columnName).ToString();
firstNameLabelControl.Text = addLineBreaksToText;
}
}
So this is how I implement the caching. My forms are based on the table names in the database so each template is cached with a reference to that table name.
protected void Page_Init(object sender, EventArgs e)
{
string tableName = Request.QueryString["table_name"].ToLower();
string id = Request.QueryString["id"];
DataDataContext data = new DataDataContext();
LinqDataSource1.TableName = tableName;
LinqDataSource1.Where = tableName + "_id = " + id;
FormView FormView1 = new FormView();
FormView1.ID = "FormView1";
FormView1.DataSourceID = "LinqDataSource1";
FormView1.CssClass = "formView";
if (id == "new")
{
InsertTemplate insertTemplate = null;
try
{
insertTemplate = (InsertTemplate)HttpContext.Current.Cache.Get(
"formDefinitionInsertTemplate:" + tableName);
}
finally
{
if (insertTemplate == null)
{
insertTemplate = new InsertTemplate();
insertTemplate.tableName = tableName;
HttpContext.Current.Cache.Insert("formDefinitionInsertTemplate:" +
tableName, insertTemplate);
}
}
FormView1.InsertItemTemplate = insertTemplate;
FormView1.Caption = "Add New " + textModification.TurnFirstToUpper(tableName);
FormView1.ChangeMode(FormViewMode.Insert);
}
else
{
FormView1.ItemUpdated += new FormViewUpdatedEventHandler(FormView1_ItemUpdated);
ItemTemplate itemTemplate = null;
try
{
itemTemplate = (ItemTemplate)Cache.Get("formDefinitionItemTemplate:" + tableName);
}
finally
{
if (itemTemplate == null)
{
itemTemplate = new ItemTemplate();
itemTemplate.tableName = tableName;
Cache.Insert("formDefinitionItemTemplate:" + tableName, itemTemplate);
}
}
EditTemplate editTemplate = null;
try
{
editTemplate =
(EditTemplate)Cache.Get("formDefinitionEditTemplate:" + tableName);
}
finally
{
if (editTemplate == null)
{
editTemplate = new EditTemplate();
editTemplate.tableName = tableName;
Cache.Insert("formDefinitionEditTemplate:" + tableName, editTemplate);
}
}
InsertTemplate insertTemplate = null;
try
{
insertTemplate =
(InsertTemplate)Cache.Get("formDefinitionInsertTemplate:" + tableName);
}
finally
{
if (insertTemplate == null)
{
insertTemplate = new InsertTemplate();
insertTemplate.tableName = tableName;
Cache.Insert("formDefinitionInsertTemplate:" + tableName, insertTemplate);
}
}
FormView1.ItemTemplate = itemTemplate;
FormView1.EditItemTemplate = editTemplate;
FormView1.InsertItemTemplate = insertTemplate;
FormView1.Caption = textModification.TurnFirstToUpper(tableName) + " Details";
}
myContainer.Controls.Add(FormView1);
}
So now that you have cached information, you won't need to recreate the template unless something changes. When something does change, all you need to do is remove the item from the cache using the code below:
Cache.Remove("formDefinitionItemTemplate:" + tableName);
I received a couple of questions about filling the template with data, so here is how it works. After creating the label or textbox, you add an event handler to call on databinding.
Label myLabel = new Label();
myLabel.DataBinding += new EventHandler(Label_DataBinding);
The code below responds when that event happens and uses the existing data to populate the control. The existing data would come from the data source that is bound to the FormView. The caveat is that the naming convention of your label must match the property name that comes from the datasource. There are other ways to accomplish this but I just use the ID of the control with the name of the control after it. Then I strip off the name of the control so I can retrieve the data.
private void Label_DataBinding(Object sender, EventArgs e)
{
Label myLabel = (Label)sender;
FormView formViewContainer = (FormView)myLabel.NamingContainer;
string columnName = myLabel.ID.Replace("Label", "");
DataRowView rowData = (DataRowView)formViewContainer.DataItem;
string textValue = rowData[columnName].ToString();
myLabel.Text = textValue;
}
Points of Interest
This performs as well as hardcods the values in the form as far as I can tell, and now I don't have to make changes when fields are added.
History
I added information about filling the label with content on databinding.