I have a datagrid. Its DataSource is an ArrayList of objects; each one has an int ID, strings for name and description. I have inline editing for the rows. When I click an update button, what I want to have happen is for the change to get committed to the database. Simple. That part I've done.
However, the name field (input via a text box) needs to be validated with a CustomValidator, which involves some codebehind which queries the database to see whether the name entered in the textbox is unique in the database.
The update-this-row click causes a postback. The button has CausesValidation set to "true". The Validator is as follows:
Code:
protected void ValidateSectionName(object source, ServerValidateEventArgs args) {
// TODO: BUG: figure out where DataItems go on postback
DataGridItem dgi = dgSections.Items[ dgSections.EditItemIndex ]; // item being edited
Section s = (Section)dgi.DataItem;
TextBox tbName = (TextBox)dgi.FindControl("tbName");
args.IsValid = s.IsUniqueName(tbName.Text.Trim());
}
This is the DataGrid declaration I'm working within (sans extraneous-to-discussion columns, for clarity):
Code:
<asp:DataGrid ID="dgSections" Runat="server" AutoGenerateColumns="False" EnableViewState="true" DataKeyField="ID" GridLines="None" cssclass="dg" AllowSorting="True">
<Columns>
<asp:TemplateColumn ItemStyle-CssClass="dg_iconcol" HeaderStyle-CssClass="dg_header_iconcol">
<ItemTemplate>
<asp:ImageButton Runat="server" Tooltip="Click to edit section"
ImageUrl="~/images/icons/edit.gif" CommandName="Edit" height="20" width="20" AlternateText="Edit section"
CommandArgument='<%# DataBinder.Eval(Container.DataItem, "ID") %>'
Visible='<%# CurrentUser.HasPermission(new Permissions[] { Permissions.DLS_EditSection, Permissions.Core_LinkGroup_Section }) %>' />
</ItemTemplate>
</asp:TemplateColumn>
<asp:TemplateColumn ItemStyle-CssClass="dg_iconcol" HeaderStyle-CssClass="dg_header_iconcol">
<ItemTemplate>
<asp:ImageButton ID="ibtnCancel" ImageUrl="~/images/icons/cancel.gif" tooltip="Click to cancel edit." Runat="server" CausesValidation="False" AlternateText="Cancel edit"
CommandName="Cancel" CommandArgument='<%# DataBinder.Eval(Container.DataItem, "ID") %>' />
</ItemTemplate>
</asp:TemplateColumn>
<asp:TemplateColumn ItemStyle-CssClass="dg_iconcol" HeaderStyle-CssClass="dg_header_iconcol">
<ItemTemplate>
<asp:ImageButton ID="ibtnSave" ImageUrl="~/images/icons/ok.gif" tooltip="Click to save changes." Runat="server" CausesValidation="False" AlternateText="Save changes"
CommandName="Update" CommandArgument='<%# DataBinder.Eval(Container.DataItem, "ID") %>' />
</ItemTemplate>
</asp:TemplateColumn>
<asp:TemplateColumn HeaderText="Name" SortExpression="Name">
<ItemTemplate>
<%# DataBinder.Eval(Container.DataItem, "Name") %><input type="hidden" id="iTimeStamp" runat="server" value='<%# Convert.ToDateTime(DataBinder.Eval(Container.DataItem, "stamp")).ToString() %>' />
</ItemTemplate>
<EditItemTemplate>
<asp:TextBox ID="tbName" Runat="server" CssClass="tb" MaxLength="50" Text='<%# DataBinder.Eval(Container.DataItem, "Name") %>' /><input type="hidden" id="iTimeStamp" runat="server" value='<%# Convert.ToDateTime(DataBinder.Eval(Container.DataItem, "stamp")).ToString() %>' />
<asp:RequiredFieldValidator ID="rfvalName" runat="server" Display="None" ErrorMessage="You must supply a section name." ControlToValidate="tbName" />
<asp:RegularExpressionValidator ID="revalName" Runat="server" Display="None" ErrorMessage="The section name must be between 1 and 50 characters long." ControlToValidate="tbName" ValidationExpression="^.{1,30}$" />
<asp:CustomValidator Runat="server" Display="None" ErrorMessage="The section name must be unique - the one you entered already exists in the database." OnServerValidate="ValidateSectionName" />
</EditItemTemplate>
</asp:TemplateColumn>
<asp:TemplateColumn HeaderText="Description" SortExpression="Description">
<ItemTemplate>
<%# (DataBinder.Eval(Container.DataItem, "Description").ToString() == "")? " ": DataBinder.Eval(Container.DataItem, "Description").ToString() %>
</ItemTemplate>
<EditItemTemplate>
<asp:TextBox ID="tbDescription" Runat="server" CssClass="tb" MaxLength="500" Text='<%# DataBinder.Eval(Container.DataItem, "Description") %>' Rows="3" TextMode="MultiLine" />
</EditItemTemplate>
</asp:TemplateColumn>
</Columns>
</asp:DataGrid>
Note ViewState is turned on.
This is my Page_Load(), and populate(mode):
Code:
private void Page_Load(object sender, System.EventArgs e) {
if (!IsPostBack) {
PFPPage.StoreBack(Session, Request.UrlReferrer);
populate(PopulateMode.View);
}
}
private void populate(PopulateMode mode) {
SortExpression = (Session["SortExpression"] == null)? "name" : Session["SortExpression"].ToString();
SortAsc = (Session["SortAsc"] == null)? true : Convert.ToBoolean(Session["SortAsc"]);
SectionList[] sl = Section.GetSections(CurrentUser,
pnlFilterControls.Visible? tbCriteria.Text.Trim(): "",
pnlFilterControls.Visible? cblFields.Items[0].Selected: false,
pnlFilterControls.Visible? cblFields.Items[1].Selected: false,
pnlFilterControls.Visible? cblFields.Items[2].Selected: false,
SortExpression,
SortAsc);
if (sl[0].Count > 0 || mode != PopulateMode.View) {
lblNoData.Visible = false;
dgSections.Visible = true;
switch (mode) {
case PopulateMode.View:
dgSections.AllowSorting = true;
dgSections.DataSource = sl[0];
dgSections.DataBind();
btnCreate.Enabled = CurrentUser.HasPermission(Permissions.DLS_EditSection);
dgSections.Columns[0].Visible = true;
dgSections.Columns[1].Visible = true;
dgSections.Columns[2].Visible = true;
dgSections.Columns[3].Visible = false;
dgSections.Columns[4].Visible = false;
dgSections.Columns[5].Visible = false;
dgSections.Columns[6].Visible = false;
dgSections.Columns[7].Visible = false;
dgSections.Columns[8].Visible = true;
btnCreate.Enabled = true;
break;
case PopulateMode.Insert:
Section s = new Section();
s.Name = "Enter name of new section here (required)...";
sl[0].Insert(0, s);
dgSections.EditItemIndex = 0;
goto case PopulateMode.Edit; // fall through, since insert is just a special case of edit...
case PopulateMode.Edit:
dgSections.AllowSorting = false;
dgSections.DataSource = sl[0];
dgSections.DataBind();
dgSections.Columns[0].Visible = false;
dgSections.Columns[1].Visible = false;
dgSections.Columns[2].Visible = false;
dgSections.Columns[3].Visible = false;
dgSections.Columns[4].Visible = false;
dgSections.Columns[5].Visible = false;
dgSections.Columns[6].Visible = true;
dgSections.Columns[7].Visible = true;
dgSections.Columns[8].Visible = true;
btnCreate.Enabled = false;
break;
}
} else {
lblNoData.Visible = true;
dgSections.Visible = false;
}
}
My question? Why is my DataSource null inside of the Validator's OnServerValidate?!
Note that I realise I can get around this by not using a CustomValidator, and instead calling the IsUniqueName function elsewhere (like inside of the DataGrid's ItemCommand event, where I have a switch statement that does different things depending on what the commandname is) just before I persist back to the database, but I feel that's cheating...