Asp.NET custom templated datalist throws argument out of range (index) on button press
- by MrTortoise
I have a class BaseTemplate
public abstract class BaseTemplate : ITemplate
This adds the controls, and provides abstract methods to implement in the inheriting class. The inheriting class then adds its html according to its data source and manages the data binding.
This all works fine - I get the control appearing with properly parsed html.
The problem is that the base class adds controls into the template that have their own CommandName arguments; the idea is that the class that implements the custom templated dataList will provide the logic of setting the Selected and Edit Indexes. This class also manages the data binding, etc. It sets all of the templates on the datalist in the Init method (which was another cause of this exception).
The exception gets thrown when I hit one of these buttons - I have tried hooking up both their click and command events everywhere in case this was the problem. I have also ensured that their command names do not match any of the system ones. The stack trace does not include any references to my methods or objects which is why I am so stuck. It is the most unhelpful message I can imagine.
The really frustrating thing is that I cannot get a breakpoint to fire - i.e. the problem is happening after I click the button, but before and of my code can execute.
The last time this exception happened was when I had this code in a user control and was assigning the templates to the datalist in the PageLoad. I moved these into init to fix that problem; however, this is a problem that was there then and I have no idea what is causing it let alone how to solve it (and index out of range doesn't really help without knowing what index.)
The Exception Details
Exception Details: System.ArgumentOutOfRangeException: Specified argument was out of the range of valid values.
Parameter name: index
The Stack Trace:
[ArgumentOutOfRangeException: Specified argument was out of the range of valid values.
Parameter name: index]
System.Web.UI.ControlCollection.get_Item(Int32 index) +8665582
System.Web.UI.WebControls.DataList.GetItem(ListItemType itemType, Int32 repeatIndex) +8667655
System.Web.UI.WebControls.DataList.System.Web.UI.WebControls.IRepeatInfoUser.GetItemStyle(ListItemType itemType, Int32 repeatIndex) +11
System.Web.UI.WebControls.RepeatInfo.RenderVerticalRepeater(HtmlTextWriter writer, IRepeatInfoUser user, Style controlStyle, WebControl baseControl) +8640873
System.Web.UI.WebControls.RepeatInfo.RenderRepeater(HtmlTextWriter writer, IRepeatInfoUser user, Style controlStyle, WebControl baseControl) +27
System.Web.UI.WebControls.DataList.RenderContents(HtmlTextWriter writer) +208
System.Web.UI.WebControls.BaseDataList.Render(HtmlTextWriter writer) +30
System.Web.UI.Control.RenderControlInternal(HtmlTextWriter writer, ControlAdapter adapter) +27
System.Web.UI.Control.RenderControl(HtmlTextWriter writer, ControlAdapter adapter) +99
System.Web.UI.Control.RenderControl(HtmlTextWriter writer) +25
System.Web.UI.Control.RenderChildrenInternal(HtmlTextWriter writer, ICollection children) +134
System.Web.UI.Control.RenderChildren(HtmlTextWriter writer) +19
System.Web.UI.HtmlControls.HtmlForm.RenderChildren(HtmlTextWriter writer) +163
System.Web.UI.HtmlControls.HtmlContainerControl.Render(HtmlTextWriter writer) +32
System.Web.UI.HtmlControls.HtmlForm.Render(HtmlTextWriter output) +51
System.Web.UI.Control.RenderControlInternal(HtmlTextWriter writer, ControlAdapter adapter) +27
System.Web.UI.Control.RenderControl(HtmlTextWriter writer, ControlAdapter adapter) +99
System.Web.UI.HtmlControls.HtmlForm.RenderControl(HtmlTextWriter writer) +40
System.Web.UI.Control.RenderChildrenInternal(HtmlTextWriter writer, ICollection children) +134
System.Web.UI.Control.RenderChildren(HtmlTextWriter writer) +19
System.Web.UI.Page.Render(HtmlTextWriter writer) +29
System.Web.UI.Control.RenderControlInternal(HtmlTextWriter writer, ControlAdapter adapter) +27
System.Web.UI.Control.RenderControl(HtmlTextWriter writer, ControlAdapter adapter) +99
System.Web.UI.Control.RenderControl(HtmlTextWriter writer) +25
System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint) +1266
The code
Base class:
public abstract class BaseTemplate : ITemplate
{
ListItemType _templateType;
public BaseTemplate(ListItemType theTemplateType)
{ _templateType = theTemplateType; }
public ListItemType ListItemType
{ get { return _templateType; } }
#region ITemplate Members
public void InstantiateIn(Control container)
{
PlaceHolder ph = new PlaceHolder();
container.Controls.Add(ph);
Literal l = new Literal();
switch (_templateType)
{
case ListItemType.Header:
{
ph.Controls.Add(new LiteralControl(@"<table><tr>"));
InstantiateInHeader(ph);
ph.Controls.Add(new LiteralControl(@"</tr>"));
break;
}
case ListItemType.Footer:
{
ph.Controls.Add(new LiteralControl(@"<tr>"));
InstantiateInFooter(ph);
ph.Controls.Add(new LiteralControl(@"</tr></table>"));
break;
}
case ListItemType.Item:
{
ph.Controls.Add(new LiteralControl(@"<tr>"));
InstantiateInItem(ph);
ph.Controls.Add(new LiteralControl(@"<td>"));
Button select = new Button();
select.ID = "btnSelect";
select.CommandName = "SelectRow";
select.Text = "Select";
ph.Controls.Add(select);
ph.Controls.Add(new LiteralControl(@"</td>"));
ph.Controls.Add(new LiteralControl(@"</tr>"));
ph.DataBinding += new EventHandler(ph_DataBinding);
break;
}
case ListItemType.AlternatingItem:
{
ph.Controls.Add(new LiteralControl(@"<tr>"));
InstantiateInAlternatingItem(ph);
ph.Controls.Add(new LiteralControl(@"<td>"));
Button select = new Button();
select.ID = "btnSelect";
select.CommandName = "SelectRow";
select.Text = "Select";
ph.Controls.Add(select);
ph.Controls.Add(new LiteralControl(@"</td>"));
ph.Controls.Add(new LiteralControl(@"</tr>"));
ph.DataBinding+=new EventHandler(ph_DataBinding);
break;
}
case ListItemType.SelectedItem:
{
ph.Controls.Add(new LiteralControl(@"<tr>"));
InstantiateInItem(ph);
ph.Controls.Add(new LiteralControl(@"<td>"));
Button edit = new Button();
edit.ID = "btnEdit";
edit.CommandName = "EditRow";
edit.Text = "Edit";
ph.Controls.Add(edit);
Button delete = new Button();
delete.ID = "btnDelete";
delete.CommandName = "DeleteRow";
delete.Text = "Delete";
ph.Controls.Add(delete);
ph.Controls.Add(new LiteralControl(@"</td>"));
ph.Controls.Add(new LiteralControl(@"</tr>"));
ph.DataBinding += new EventHandler(ph_DataBinding);
break;
}
case ListItemType.EditItem:
{
ph.Controls.Add(new LiteralControl(@"<tr>"));
InstantiateInEdit(ph);
ph.Controls.Add(new LiteralControl(@"<td>"));
Button save = new Button();
save.ID = "btnSave";
save.CommandName = "SaveRow";
save.Text = "Save";
ph.Controls.Add(save);
Button cancel = new Button();
cancel.ID = "btnCancel";
cancel.CommandName = "CancelRow";
cancel.Text = "Cancel";
ph.Controls.Add(cancel);
ph.Controls.Add(new LiteralControl(@"</td>"));
ph.Controls.Add(new LiteralControl(@"</tr>"));
ph.DataBinding += new EventHandler(ph_DataBinding);
break;
}
case ListItemType.Separator:
{
InstantiateInSeperator(ph);
break;
}
}
}
void ph_DataBinding(object sender, EventArgs e)
{
DataBindingOverride(sender, e);
}
/// <summary>
/// the controls placed into the PlaceHolder will get wrapped in <table><tr> </tr>. I.e. you need to provide the column names wrapped in <td></td> tags.
/// </summary>
/// <param name="header"></param>
public abstract void InstantiateInHeader(PlaceHolder ph);
/// <summary>
/// the controls will have a column added after them and so require each column to be properly wrapped in <td></td> tags. The <tr></tr> is handled in the base class.
/// </summary>
/// <param name="ph"></param>
public abstract void InstantiateInItem(PlaceHolder ph);
/// <summary>
/// the controls will have a column added after them and so require each column to be properly wrapped in <td></td> tags. The <tr></tr> is handled in the base class.
/// </summary>
/// <param name="ph"></param>
public abstract void InstantiateInAlternatingItem(PlaceHolder ph);
/// <summary>
/// the controls will have a column added after them and so require each column to be properly wrapped in <td></td> tags. The <tr></tr> is handled in the base class.
/// </summary>
/// <param name="ph"></param>
public abstract void InstantiateInEdit(PlaceHolder ph);
/// <summary>
/// Any html used in the footer will have </tr><table> appended to the end.
/// <tr> will be appended to the front.
/// </summary>
/// <param name="ph"></param>
public abstract void InstantiateInFooter(PlaceHolder ph);
/// <summary>
/// the controls will have a column added after them and so require each column to be properly wrapped in <td></td> tags. The <tr></tr> is handled in the base class.
/// Adds Delete and Edit Buttons after the table contents.
/// </summary>
/// <param name="ph"></param>
public abstract void InstantiateInSelectedItem(PlaceHolder ph);
/// <summary>
/// The base class provides no <tr></tr> tags
/// </summary>
/// <param name="ph"></param>
public abstract void InstantiateInSeperator(PlaceHolder ph);
/// <summary>
/// Use this method to bind the controls to their data.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
public abstract void DataBindingOverride(object sender, EventArgs e);
#endregion
}
Inheriting class:
public class NominalGroupTemplate : BaseTemplate
{
public NominalGroupTemplate(ListItemType theListItemType)
: base(theListItemType) { }
public override void InstantiateInHeader(PlaceHolder ph)
{
ph.Controls.Add(new LiteralControl(@"<td>ID</td><td>Group</td><td>IsPositive</td>"));
}
public override void InstantiateInItem(PlaceHolder ph)
{
ph.Controls.Add(new LiteralControl(@"<td>"));
Label lblID = new Label();
lblID.ID = "lblID";
ph.Controls.Add(lblID);
ph.Controls.Add(new LiteralControl(@"</td><td>"));
Label lblGroup = new Label();
lblGroup.ID = "lblGroup";
ph.Controls.Add(lblGroup);
ph.Controls.Add(new LiteralControl(@"</td><td>"));
CheckBox chkIsPositive = new CheckBox();
chkIsPositive.ID = "chkIsPositive";
chkIsPositive.Enabled = false;
ph.Controls.Add(chkIsPositive);
ph.Controls.Add(new LiteralControl(@"</td>"));
}
public override void InstantiateInAlternatingItem(PlaceHolder ph)
{
InstantiateInItem(ph);
}
public override void InstantiateInEdit(PlaceHolder ph)
{
ph.Controls.Add(new LiteralControl(@"<td>"));
Label lblID = new Label();
lblID.ID = "lblID";
ph.Controls.Add(lblID);
ph.Controls.Add(new LiteralControl(@"</td><td>"));
TextBox txtGroup = new TextBox();
txtGroup.ID = "txtGroup";
txtGroup.Visible = true;
txtGroup.Enabled = true ;
ph.Controls.Add(txtGroup);
ph.Controls.Add(new LiteralControl(@"</td><td>"));
CheckBox chkIsPositive = new CheckBox();
chkIsPositive.ID = "chkIsPositive";
chkIsPositive.Visible = true;
chkIsPositive.Enabled = true ;
ph.Controls.Add(chkIsPositive);
ph.Controls.Add(new LiteralControl(@"</td>"));
}
public override void InstantiateInFooter(PlaceHolder ph)
{
InstantiateInHeader(ph);
}
public override void InstantiateInSelectedItem(PlaceHolder ph)
{
ph.Controls.Add(new LiteralControl(@"<td>"));
Label lblID = new Label();
lblID.ID = "lblID";
ph.Controls.Add(lblID);
ph.Controls.Add(new LiteralControl(@"</td><td>"));
TextBox txtGroup = new TextBox();
txtGroup.ID = "txtGroup";
txtGroup.Visible = true;
txtGroup.Enabled = false;
ph.Controls.Add(txtGroup);
ph.Controls.Add(new LiteralControl(@"</td><td>"));
CheckBox chkIsPositive = new CheckBox();
chkIsPositive.ID = "chkIsPositive";
chkIsPositive.Visible = true;
chkIsPositive.Enabled = false;
ph.Controls.Add(chkIsPositive);
ph.Controls.Add(new LiteralControl(@"</td>"));
}
public override void InstantiateInSeperator(PlaceHolder ph)
{
}
public override void DataBindingOverride(object sender, EventArgs e)
{
PlaceHolder ph = (PlaceHolder)sender;
DataListItem li = (DataListItem)ph.NamingContainer;
int id = Convert.ToInt32(DataBinder.Eval(li.DataItem, "ID"));
string group = (string)DataBinder.Eval(li.DataItem, "Group");
bool isPositive = Convert.ToBoolean(DataBinder.Eval(li.DataItem, "IsPositive"));
switch (this.ListItemType)
{
case ListItemType.Item:
case ListItemType.AlternatingItem:
{
((Label)ph.FindControl("lblID")).Text = id.ToString();
((Label)ph.FindControl("lblGroup")).Text = group;
((CheckBox)ph.FindControl("chkIsPositive")).Text = isPositive.ToString();
break;
}
case ListItemType.EditItem:
case ListItemType.SelectedItem:
{
((TextBox)ph.FindControl("lblID")).Text = id.ToString();
((TextBox)ph.FindControl("txtGroup")).Text = group;
((CheckBox)ph.FindControl("chkIsPositive")).Text = isPositive.ToString();
break;
}
}
}
}
From here I added the control to a page the code behind
public partial class NominalGroupbroke : System.Web.UI.UserControl
{
public void SetNominalGroupList(IList<BONominalGroup> theNominalGroups)
{
XElement data = Serialiser<BONominalGroup>.SerialiseObjectList(theNominalGroups);
ViewState.Add("nominalGroups", data.ToString());
dlNominalGroup.DataSource = theNominalGroups;
dlNominalGroup.DataBind();
}
protected void Page_init()
{
dlNominalGroup.HeaderTemplate = new NominalGroupTemplate(ListItemType.Header);
dlNominalGroup.ItemTemplate = new NominalGroupTemplate(ListItemType.Item);
dlNominalGroup.AlternatingItemTemplate = new NominalGroupTemplate(ListItemType.AlternatingItem);
dlNominalGroup.SeparatorTemplate = new NominalGroupTemplate(ListItemType.Separator);
dlNominalGroup.SelectedItemTemplate = new NominalGroupTemplate(ListItemType.SelectedItem);
dlNominalGroup.EditItemTemplate = new NominalGroupTemplate(ListItemType.EditItem);
dlNominalGroup.FooterTemplate = new NominalGroupTemplate(ListItemType.Footer);
}
protected void Page_Load(object sender, EventArgs e)
{
dlNominalGroup.ItemCommand += new DataListCommandEventHandler(dlNominalGroup_ItemCommand);
}
void dlNominalGroup_Init(object sender, EventArgs e)
{
dlNominalGroup.HeaderTemplate = new NominalGroupTemplate(ListItemType.Header);
dlNominalGroup.ItemTemplate = new NominalGroupTemplate(ListItemType.Item);
dlNominalGroup.AlternatingItemTemplate = new NominalGroupTemplate(ListItemType.AlternatingItem);
dlNominalGroup.SeparatorTemplate = new NominalGroupTemplate(ListItemType.Separator);
dlNominalGroup.SelectedItemTemplate = new NominalGroupTemplate(ListItemType.SelectedItem);
dlNominalGroup.EditItemTemplate = new NominalGroupTemplate(ListItemType.EditItem);
dlNominalGroup.FooterTemplate = new NominalGroupTemplate(ListItemType.Footer);
}
void dlNominalGroup_DataBinding(object sender, EventArgs e)
{
}
void deleteNominalGroup(int index)
{
XElement data = XElement.Parse(Convert.ToString( ViewState["nominalGroups"] ));
IList<BONominalGroup> list = Serialiser<BONominalGroup>.DeserialiseObjectList(data);
FENominalGroup.DeleteNominalGroup(list[index].ID);
list.RemoveAt(index);
data = Serialiser<BONominalGroup>.SerialiseObjectList(list);
ViewState["nominalGroups"] = data.ToString();
dlNominalGroup.DataSource = list;
dlNominalGroup.DataBind();
}
void updateNominalGroup(DataListItem theItem)
{
XElement data = XElement.Parse(Convert.ToString( ViewState["nominalGroups"]));
IList<BONominalGroup> list = Serialiser<BONominalGroup>.DeserialiseObjectList(data);
BONominalGroup old = list[theItem.ItemIndex];
BONominalGroup n = new BONominalGroup();
byte id = Convert.ToByte(((TextBox)theItem.FindControl("lblID")).Text);
string group = ((TextBox)theItem.FindControl("txtGroup")).Text;
bool isPositive = Convert.ToBoolean(((CheckBox)theItem.FindControl("chkIsPositive")).Text);
n.ID = id;
n.Group = group;
n.IsPositive = isPositive;
FENominalGroup.UpdateNominalGroup(old, n);
list[theItem.ItemIndex] = n;
data = Serialiser<BONominalGroup>.SerialiseObjectList(list);
ViewState["nominalGroups"] = data.ToString();
}
void dlNominalGroup_ItemCommand(object source, DataListCommandEventArgs e)
{
DataList l = (DataList)source;
switch (e.CommandName)
{
case "SelectRow":
{
if (l.EditItemIndex == -1)
{
l.SelectedIndex = e.Item.ItemIndex;
l.EditItemIndex = -1;
}
break;
}
case "EditRow":
{
if (l.SelectedIndex == e.Item.ItemIndex)
{
l.EditItemIndex = e.Item.ItemIndex;
}
break;
}
case "DeleteRow":
{
deleteNominalGroup(e.Item.ItemIndex);
l.EditItemIndex = -1;
try
{
l.SelectedIndex = e.Item.ItemIndex;
}
catch
{
l.SelectedIndex = -1;
}
break;
}
case "CancelRow":
{
l.SelectedIndex = l.EditItemIndex;
l.EditItemIndex = -1;
break;
}
case "SaveRow":
{
updateNominalGroup(e.Item);
try
{
l.SelectedIndex = e.Item.ItemIndex;
}
catch
{
l.SelectedIndex = -1;
}
l.EditItemIndex = -1;
break;
}
}
}
Lots of code there, I'm afraid, but it should build.
Thanks if anyone manages to spot my silliness.
The BONominalGroup class (please ignore my crazy getHash override, I'm not proud of it).
IAudit can just be an empty interface here and all will be fine.
It used to inherit from another class, I have cleaned that out - so the serialization logic may be broken here.
public class BONominalGroup
{
public BONominalGroup()
#region Fields and properties
private Int16 _ID;
public Int16 ID
{
get { return _ID; }
set { _ID = value; }
}
private string _group;
public string Group
{
get { return _group; }
set { _group = value; }
}
private bool _isPositve;
public bool IsPositive
{
get { return _isPositve; }
set { _isPositve = value; }
}
#endregion
public override bool Equals(object obj)
{
bool retVal = false;
BONominalGroup ng = obj as BONominalGroup;
if (ng!=null)
if (ng._group == this._group &&
ng._ID == this.ID &&
ng.IsPositive == this.IsPositive)
{
retVal = true;
}
return retVal;
}
public override int GetHashCode()
{
return ToString().GetHashCode();
}
public override string ToString()
{
return "BONominalGroup{ID:" + this.ID.ToString() +
",Group:" + this.Group.ToString() +
",IsPositive:" + this.IsPositive.ToString() +
"," + "}";
}
#region IXmlSerializable Members
public override void ReadXml(XmlReader reader)
{
reader.ReadStartElement("BONominalGroup");
this.ID = Convert.ToByte(reader.ReadElementString("id"));
this.Group = reader.ReadElementString("group");
this.IsPositive = Convert.ToBoolean(reader.ReadElementString("isPositive"));
base.ReadXml(reader);
reader.ReadEndElement();
}
public override void WriteXml(XmlWriter writer)
{
writer.WriteElementString("id", this.ID.ToString());
writer.WriteElementString("group", this.Group);
writer.WriteElementString("isPositive", this.IsPositive.ToString());
// writer.WriteStartElement("BOBase");
// base.WriteXml(writer);
writer.WriteEndElement();
}
#endregion
}