views:

63

answers:

4

I have a page containing a control called PhoneInfo.ascx. PhoneInfo is dynamically created using LoadControl() and then the initControl() function is called passing in an initialization object to set some initial textbox values within PhoneInfo.

The user then changes these values and hits a submit button on the page which is wired up to the "submit_click" event. This event invokes the GetPhone() function within PhoneInfo. The returned value has all of the new user entered values except that the phoneId value (stored in ViewState and NOT edited by the user) always comes back as null.

I believe that the viewstate is responsible for keeping track of user entered data across a postback, so I can't understand how the user values are coming back but not the explicitly set ViewState["PhoneId"] value! If I set the ViewState["PhoneId"] value in PhoneInfo's page_load event, it retrieves it correctly after the postback, but this isn't an option because I can only initialize that value when the page is ready to provide it.

I'm sure I am just messing up the page lifecycle somehow, any suggestion or questions would really help! I have included a much simplified version of the actual code below.

Containing page's codebehind

protected void Page_Load(object sender, EventArgs e)
{
    Phone phone = controlToBind as Phone;
    PhoneInfo phoneInfo = (PhoneInfo)LoadControl("phoneInfo.ascx"); //Create phoneInfo control
    phoneInfo.InitControl(phone); //use controlToBind to initialize the new control
    Controls.Add(phoneInfo);
}
protected void submit_click(object sender, EventArgs e)
{
    Phone phone = phoneInfo.GetPhone();
}

PhoneInfo.ascx codebehind

protected void Page_Load(object sender, EventArgs e)
{
}

public void InitControl(Phone phone)
{
    if (phone != null)
    {
        ViewState["PhoneId"] = phone.Id;
        txt_areaCode.Text = SafeConvert.ToString(phone.AreaCode);
        txt_number.Text =  SafeConvert.ToString(phone.Number);
        ddl_type.SelectedValue = SafeConvert.ToString((int)phone.Type);
    }
}

public Phone GetPhone()
{
    Phone phone = new Phone();
    if ((int)ViewState["PhoneId"] >= 0)
        phone.Id = (int)ViewState["PhoneId"];
        phone.AreaCode = SafeConvert.ToInt(txt_areaCode.Text);
        phone.Number = SafeConvert.ToInt(txt_number.Text);
        phone.Type = (PhoneType)Enum.ToObject(typeof(PhoneType),       SafeConvert.ToInt(ddl_type.SelectedValue));
        return phone;
    }
}
A: 

ViewState is managed between the Init and Load events. You should utilize code in or functions called from the Page_Init handler instead of Page_Load when you are concerned with maintaining values from postbacks.

See this link for more information on the ASP.NET page life cycle.

http://msdn.microsoft.com/en-us/library/ms178472.aspx

Anthony Pegram
I understand this portion of the lifecycle and agree that the best place for this is on page_init. But why do the user-entered values make it through to my GetPhone function if they were saved through postback by viewstate? Shouldn't those have come back as null also?
caltrop
A: 

To do exactly what you're doing, you have to have the control loaded in the Init portion of the page lifecycle.

However, to do it more easily, why don't you add it to an <asp:ContentPlaceHolder>? I think it will make this much easier.

Jaxidian
Thanks for the placeholder suggestion, but because of the complexity of the code I have left out, a placeholder is not ideal here. =)I understand the lifecycle enough to know that putting this in Init will cause the ViewState to save and be loaded properly... I guess I'm confused as to why the user-entered text DOES work considering it goes through the viewstate as well.
caltrop
A: 

Based on the comment responses, I'm adding another answer here that explains a part of the Page Lifecycle and what it means for post and viewstate data. Below is a more in-English (to a dev) and less about events version of the beginning of the page lifecycle:

  1. User requests a page (request 1)
  2. ASP.NET builds the page with default values in fields (request 1)
  3. On your Page_Load, you add this special control with your default values (request 1)
  4. You send this page to the user (request 1)
  5. The user posts data to the page (request 2)
  6. ASP.NET builds the page from your ASPX file (request 2)
  7. ASP.NET adds the ViewState data to the page (at this time, there is none) (request 2)
  8. ASP.NET adds the Post data to the page (request 2)
  9. ASP.NET runs reaches the Load step of the Page Lifecycle and adds your special control with your default values (request 2)
  10. You are now in the problematic state that you are seeing.

So the problem here is that your dynamically-loaded control does not exist when post data is added to the page. When you add it, you put your default values in. ASP.NET no longer has a chance to populate it with the proper data.

Jaxidian
For what it's worth, the `Init` event is between steps 6 and 7 of this summary. More specifically, step 6 is the `PreInit` event.
Jaxidian
That makes a lot of sense. I wish my actual code would allow me to simply move the creation code to Page_init, but it doesn't. I'll have to figure out some way to move things around. Thanks for the help!
caltrop
A: 

I thought, like the answerers, that this problem existed because I was setting the ViewState variable too late to be saved during SaveViewState. However, I added an override for the SaveViewState event, and sure enough I was setting my ViewState variable long before the ViewState was saved. Too long before, it turns out.

Apparently after initialization, .net runs TrackViewState() which actually starts listening for any viewState variables you add. Since I had set this viewState value BEFORE TrackViewState() had run, everything appeared fine but the values were not actually being added during SaveViewState. I explicitly called TrackViewState() right before setting the variable and everything worked as expected.

My final solution was to assign a private property to hold the ID value through initialization and then to set the viewState variable during SaveViewState from the property value. This way I can ensure that TrackViewState has been run by .net without having to explicitly call it and mess up the flow of things. I reload the viewState value on page_load and use it to set the value of the property which I can then use in my GetPhone function.

This article talks about enableViewState.

private int _id = -1;

protected void Page_Load(object sender, EventArgs e)
{
    if (ViewState["PhoneId"] != null)
    _id = SafeConvert.ToInt(ViewState["PhoneId"]);
}

protected override object SaveViewState()
{
    ViewState["PhoneId"] = _id;
    return base.SaveViewState();
}

public Phone GetPhone()
{
    Phone phone = new Phone();
    phone.Id = _id;
    phone.AreaCode = SafeConvert.ToInt(txt_areaCode.Text);
    phone.Number = SafeConvert.ToInt(txt_number.Text);
    return phone;
}
caltrop