Search code examples
c#asp.nethidden-field

How to update a hidden field's value in code behind


After Albert's brilliant answer about GridViews it became clear to me that the original wording of the question led to confusion. The GridView is just an element on the control - for all extents and purposes it could just be a button that has a value stored in a datatag.

For clarification purposes, I will post the full markup for the page and inner control, as well as the full code in it's current form.

Here's the ascx markup:

<%@ Control Language="C#" AutoEventWireup="true" CodeFile="MyClasses.ascx.cs" Inherits="Utilities_CourseChange_MyClasses" %>

<style>
    .hiddenCol {
        display: none !important;
    }
</style>

<asp:Panel ID="Panel1" runat="server">
    <asp:UpdatePanel ID="UpdatePanel1" runat="server" UpdateMode="Conditional">
        <ContentTemplate>
            <div style="height: 8px;"></div>
            <asp:HiddenField ID="hfTEST" runat="server" />
            <span style="width: 100%; display: inline-block; text-align: center; font-size: 8pt;">Tutor Groups</span>
            <asp:GridView ID="gvTutorGroups" runat="server" AutoGenerateColumns="False" DataSourceID="sqlTutorGroups" DataKeyNames="TTGP_Group_Code" AllowPaging="True" PageSize="8" EmptyDataText="You have no tutor groups to display." Style="margin: 0 auto; width: 870px;" OnRowDataBound="gvTutorGroups_RowDataBound" OnSelectedIndexChanged="gvTutorGroups_SelectedIndexChanged">
                <Columns>
                    <asp:TemplateField ItemStyle-CssClass="hiddenCol" HeaderStyle-CssClass="hiddenCol">
                        <ItemTemplate>
                            <asp:HiddenField runat="server" ID="hfTTGPISN" Value='<%# Eval("TTGP_ISN") %>' />
                        </ItemTemplate>
                    </asp:TemplateField>
                    <asp:BoundField DataField="TTGP_Group_Code" HeaderText="TG Code" />
                    <asp:BoundField DataField="PRPH_Title" HeaderText="Name" />
                    <asp:BoundField DataField="TTGP_Start_Date" HeaderText="Start Date" DataFormatString="{0:d}" />
                    <asp:BoundField DataField="TTGP_End_Date" HeaderText="End Date" DataFormatString="{0:d}" />
                </Columns>
            </asp:GridView>
            <asp:LinkButton ID="lnkDummy" runat="server"></asp:LinkButton>

            <asp:SqlDataSource ID="sqlTutorGroups" runat="server" ConnectionString="My connection string" SelectCommand="My silly little database query - has two parameters, and spit out the values for the grid">
                <SelectParameters>
                    <asp:Parameter DefaultValue="<%$ AppSettings:CurrentAcademicYear %>" Type="String" Name="YearRef" />
                </SelectParameters>
            </asp:SqlDataSource>

            <div style="height: 8px;"></div>
        </ContentTemplate>
    </asp:UpdatePanel>
</asp:Panel>

Now I could pull the GridView out of the UpdatePanel but I'm not sure what difference it would make.

In the code behind for the `ASCX', I have this:

using System;
using System.Data;
using System.Web;
using System.Web.UI.WebControls;

public partial class Utilities_CourseChange_MyClasses : System.Web.UI.UserControl
{
    protected void gvTutorGroups_RowDataBound(object sender, GridViewRowEventArgs e)
    {
        if (e.Row.RowType == DataControlRowType.DataRow)
        {
            //Change the mouse cursor to Hand symbol to show the user the cell is selectable
            e.Row.Attributes["onmouseover"] = "this.style.textDecoration='underline';this.style.cursor='Pointer'";
            e.Row.Attributes["onmouseout"] = "this.style.textDecoration='none';";
            e.Row.Attributes["onclick"] = Page.ClientScript.GetPostBackClientHyperlink(gvTutorGroups, "Select$" + e.Row.RowIndex);
        }
    }

    protected void gvTutorGroups_SelectedIndexChanged(object sender, EventArgs e)
    {
        foreach (GridViewRow row in gvTutorGroups.Rows)
        {
            if (row.RowIndex == gvTutorGroups.SelectedIndex)
            {
                row.CssClass = "rowSelected";

                DataRowView dataItem = (DataRowView)row.DataItem; //An unreferenced remnant of a previous attempt that I forgot to delete
                HiddenField hfTGIsn = (HiddenField)this.Parent.FindControl("hfTGisn"); //Hidden field on the parent page, NOT the ASCX
                hfTGIsn.Value = ((HiddenField)row.FindControl("hfTTGPISN")).Value;
                hfTEST.Value = ((HiddenField)row.FindControl("hfTTGPISN")).Value; // I've put this in to test whether or not it's an issue with everything being reset, or just passing it up to the parent page that's playing up
            }
            else
            {
                row.CssClass = "";
            }
        }
    }

    protected void Page_Load(object sender, EventArgs e)
    {
        if (!Page.IsPostBack)
        {
            var loginName = HttpContext.Current.User.Identity.Name.ToLowerInvariant().Trim().Replace("domainName", "");
            sqlTutorGroups.SelectParameters.Add("UserName", loginName);
        }
    }
}

Now, I'm aware this isn't necessarily the best way of doing it - but it's a legacy system that we're maintaining while writing a new system in Blazor to eventually replace it. So I'm not necessarily looking for the best way of doing it, just a way that will work - I've lifted half of this code from other parts of the system to pull it into a centralised page.

Now the parent ASPX currently looks like this:

<%@ Page Title="Course Change Tool" Language="C#" MasterPageFile="~/Site.master" AutoEventWireup="true" CodeFile="ProgCoachChangeRequester.aspx.cs" Inherits="Utilities_CourseChange_ProgCoachChangeRequester" EnableEventValidation="false" %>

<%@ Register Src="~/Utilities/CourseChange/MyClasses.ascx" TagName="Groups" TagPrefix="uc1" %>

<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="Server">
    <asp:HiddenField ID="hfTGisn" runat="server" />
    <div class="content">
        <uc1:Groups ID="MyGroups" runat="server"></uc1:Groups>
    </div>

    <div runat="server" id="testDiv"></div>
</asp:Content>

With the code behind literally being as simple as:

using System;
using System.Web.UI;
using System.Web.UI.WebControls;

public partial class Utilities_CourseChange_ProgCoachChangeRequester : Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        hfTGisn.Value = ((HiddenField)MyGroups.FindControl("hfTEST")).Value;
        
        testDiv.InnerText = hfTGisn.Value.ToString();
    }
}

Now, everything in the ASCX seems to be working perfectly - my issue is that, when stepping through the code I can see the values being updated as I'd expect; but the end product in the dev tools window on Chrome indicates that, while the hfTEST (that hidden field I'd put in the ascx to test the mechanism) value is getting updated, the value for hfTGisn is not. As is evident in this screencap: Developer Tool representation of the rendered page after selecting a row

I don't really care too much how I get the value into the parent ASPX (at this point, for all the hassle it's giving me, I'm half debating just pulling it all out of the ASCX and just shoving it all into the ASPX) - I just need the value so that I can start writing the rest of the page.

So, what's the easiest way to pluck the value from within the ascx, to the parent page?


Update with further attempt:

Following the suggestion of another answer, I attempted to utilise the ViewState, changing the code behind the ASPX page to:

protected void Page_Load(object sender, EventArgs e)
{
    hfTGisn.Value = HfTGisn;

    testDiv.InnerText = HfTGisn;
}

public string HfTGisn
{
    get
    {
        return (string)ViewState["hfTGisn"];
    }
    set
    {
        ViewState["hfTGisn"] = value;
    }
}

And changing the gvTutorGroups_SelectedIndexChanged method to:

protected void gvTutorGroups_SelectedIndexChanged(object sender, EventArgs e)
{
    foreach (GridViewRow row in gvTutorGroups.Rows)
    {
        if (row.RowIndex == gvTutorGroups.SelectedIndex)
        {
            row.CssClass = "rowSelected";

            HiddenField hfTGIsn = (HiddenField)this.Parent.FindControl("hfTGisn");
            hfTGIsn.Value = ((HiddenField)row.FindControl("hfTTGPISN")).Value;
            hfTEST.Value = ((HiddenField)row.FindControl("hfTTGPISN")).Value;
            ViewState["hfTGisn"] = hfTGIsn.Value;
        }
        else
        {
            row.CssClass = "";
        }
    }
}

But, seemingly the ViewState isn't populated in Page_Load


Solution

  • In the case of multiple postbacks you could use ViewState.

    Because web application is stateless, after a request the entire page and its controls are created again and the previous page, controls and their values are lost. ViewState helps managing the page's state.

    You might try something like this:

    public string HfTGisn
    {
       get
       {
          return (string)ViewState["hfTGisn"];
       }
       set
       {
          ViewState["hfTGisn"] = value;
       }
    }
    

    In code behind add HfTGisn as ProgCoachChangeRequester partial class property.

    In Page_Load add

    hfTGisn.Value = HfTGisn;
    

    In gvTutorGroups_SelectedIndexChanged event handler add

    HfTGisn = ((HiddenField)row.FindControl("hfTTGPISN")).Value;
    hfTGisn.Value = HfTGisn;
    

    As Poul Bak stated, you can already access ascx control hfTGisn and I think it is the same for all other controls, including hfTTGPISN.

    This way selected ID from GridView is saved in HfTGisn property (essentially stored in ViewState) and therefor it is not lost. HfTGisn property's value is then stored as value of hfTGisn hidden field, both in gvTutorGroups_SelectedIndexChanged event handler and in Page_Load.

    You've said that hfTGisn hidden field's value is read in Page_Load, so I suppose you could read it directly from the property. Even more, you maybe don't even need the hidden field, if its only purpose is to store the selected ID from GridView.

    This is quick fix, but I think it is more important to find where is the second postback coming from. I have no other idea than debugging every step after gvTutorGroups_SelectedIndexChanged is executed.

    Edit: Maybe the simples solution is to read selected ID from GridView in Page_Load, and store it as hidden field's value. GridView and other controls store their value in ViewState by default.

    Edit 2: Thank you for clarification.

    I didn't manage to reproduce the issue, I've successfully passed value from control to parent's hidden field. However, I think there is a way to fix the problem you are facing, although the solution is far from good, but it works (at least I hope it will).

    All other changes proposed in this answer should be rejected.

    I still suspect hidden field's value is reset after some additional request. Instead of ViewState let's use Session. ViewState cannot be shared between page and its controls. Session can be shared through entire application, during single session.

    In event handler store ID of GridView in Session and name it to be unique (you could store the name somewhere where you can access it easily from any part of code). Don't forget to store the value to hidden field, too:

    protected void gvTutorGroups_SelectedIndexChanged(object sender, EventArgs e)
    {
        foreach (GridViewRow row in gvTutorGroups.Rows)
        {
            if (row.RowIndex == gvTutorGroups.SelectedIndex)
            {
                // ...
                
                Session["Utilities_CourseChange_MyClasses_gvTutorGroups_SelectedIndexChanged"] = ((HiddenField)row.FindControl("hfTTGPISN")).Value;
                
                HiddenField hfTGIsn = (HiddenField)this.Parent.FindControl("hfTGisn");
                hfTGIsn.Value = Session["Utilities_CourseChange_MyClasses_gvTutorGroups_SelectedIndexChanged"];
                
                // ...
            }
            else
            {
                row.CssClass = "";
            }
        }
    }
    

    In Page_Load of the parent page store value from session to hidden field and it should be always executed, not only in case of postback:

    using System;
    using System.Web.UI;
    using System.Web.UI.WebControls;
    
    public partial class Utilities_CourseChange_ProgCoachChangeRequester : Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            hhfTGIsn.Value = Session["Utilities_CourseChange_MyClasses_gvTutorGroups_SelectedIndexChanged"];
        }
    }