GridView Loses DataKeys - asp.net

Why is it that when I try to set my GridView's sorting using session states that suddenly my GridView no longer has DataKeys? All I did was put the following code into my Page_Init;
Dim SortDirection As String = Session("SortDir")
Dim sortExpression As String = Session("SortExp")
If Not SortDirection Is Nothing AndAlso Not sortExpression Is Nothing Then
If (SortDirection = "asc") Then
GridView1.Sort(sortExpression, WebControls.SortDirection.Ascending)
Else
GridView1.Sort(sortExpression, WebControls.SortDirection.Descending)
End If
End If
But if I comment this out than my other methods don't crash out any more as my GridView now has it's DataKeys. Why is this?
UPDATE
This is the exact line that stops working when the above code is in place.
Dim UserID = GridView1.DataKeys(e.RowIndex).Value.ToString
According to debugger GridView1 has columns but it's DataKeys Count is 0. The error I receive is;
Index was out of range. Must be non-negative and less than the size of the collection.
Parameter name: index

You want to perform those actions in the Page_Load (possibly in a If Not Page.IsPostBack block) event, not in the Page_Init event. Init is for initializing or reading control properties; Load is where you generally set properties (like sort direction, etc).
Basically, your ViewState hasn't been loaded in Page_Init yet. So, you modify the controls properties in Init, then some properties get filled out from the ViewState, and this leads to unexpected behavior when your Page does the Load event (which recursively calls each server control's Load event).
You can read all about this (somewhat confusing) topic on MSDN: ASP.NET Page Life Cycle Overview

Related

Storing and restoring properties in ASP.NET derived control

I have created an ASP.NET class derived from the standard WebControls.TextBox, with the intention of adding extra properties that will persist between post-backs. However, I cannot figure how to get the values in these properties to persist.
I have tried setting the value of the properties into the controls ViewState as part of the PreRender handler, but the value is then not accessible in the Init handler on the post-back, because the ViewState has not yet been setup.
I could look for the ViewState value in the Load handler of the control, but if the page/usercontrol that is using the control asks for the properties value during its Load handler, the control hasn't yet reached it's Load handler, and it therefore not there.
My current class looks something like this...
Public Class MyTextBox
Inherits TextBox
Private _myParam As String = ""
Public Property MyParam As String
Get
Return _myParam
End Get
Set(value As String)
_myParam = value
End Set
End Property
Private Sub MyTextBox_Init(sender As Object, e As EventArgs) Handles Me.Init
If Page.IsPostBack Then
_myParam = ViewState("myParam")
End If
End Sub
Private Sub MyTextBox_PreRender(sender As Object, e As EventArgs) Handles Me.PreRender
ViewState("myParam") = _myParam
End Sub
End Class
I must be missing something really simple, such as whether there is an attribute I can set against the property.
UPDATE
Thanks to #AVD pointing out that I really had very little clue about the ViewState and the Init / Load stages, I finally figured it all out.
If you have the time (and if you're doing any major ASP.NET work, you need to make the time) please read the Understand ASP.NET View State document that #AVD pointed me to. It will explain a lot.
However, what it didn't explain is if you place your control within a <asp:Repeater>, you may as well throw all the rules out of the window... and that is exactly the problem I was experiencing.
In the end, the way I managed to get it to work was to use a <asp:PlaceHolder> control within the repeater, create an instance of my control within the ItemDataBound handler of the repeater, and then add the control to the <asp:PlaceHolder>... all done within the Init section (which fortunately I'm able to do).
As Andrew found out in this previous question you can end up in a chicken/egg situation, where you need to create the controls in the Init, but you won't know what controls you need until the Load.
(I have still made AVD's answer the correct one, because in the context of my original question, it is absolutely correct).
You have to store/retrieve value to/from ViewState within the properties accessors.
Public Property MyParam As String
Get
If IsNothing(ViewState("myParam")) Then
return string.Empty
End IF
Return ViewState("myParam").ToString()
End Get
Set(value As String)
ViewState("myParam") = value
End Set
End Property

How to use a viewstate'd object as a datasource for controls on a user control

I've got a listview on a control. Each row comprises a checkbox and another listview.
The outer listview is bound to a property on the control (via a method call, can't set a property as a SelectMethod on an ObjectDataSource it would appear) which is lazy loaded suchly:
Public ReadOnly Property ProductLineChargeDetails() As List(Of WebServiceProductLineChargeDetail)
Get
If ViewState("WSProductLineChargeDetails") Is Nothing Then
ViewState("WSProductLineChargeDetails") = GetWebServiceProductLineChargeDetails()
End If
Return DirectCast(ViewState("WSProductLineChargeDetails"), Global.System.Collections.Generic.List(Of Global.MI.Open.WebServiceProductLineChargeDetail))
End Get
End Property
The shape of the object referenced by the data source is something like this:
(psuedocode)
Product
{
bool Licenced;
List<Charge> charges;
}
Charge
{
int property1;
string property2;
bool property3
.
.
.
}
The reason for the use of viewstate is this:
When an one of the checkboxes on one of the outer list view rows is checked or unchecked I want to modify the object that the ODS represents (for example I'll add a couple of Charge objects to the relevant Product object) and then rebind.
The problem I'm getting is that after every postback (specifically after checking or unchecking one of the rows' checkbox) my viewstate is empty. Thiss means that any changes I make to my viewstate'd object is lost.
Now, I've worked out (after much googling and reading, amongst many others, Scott Mitchel's excellent bit on ViewState) that during initial databinding IsTrackingViewState is set to false. That means, I think, that assigning the return from GetWebServiceProductLineChargeDetails() to the ViewState item in my Property Get during the initial databind won't work.
Mind you, even when the IsTrackingViewState is true and I call the Property Get, come the next postback, the viewstate is empty.
So do you chaps have any ideas on how I keep the object referenced by the ObjectDataSource in ViewState between postbacks and update it and get those changes to stay in ViewState? This has been going on for a couple of days now and I'm getting fed up!
Cheers in advance
Steve
In order for changes to be tracked in the viewstate the object must be assigned to the viewstate when IsTrackingViewState is True. Viewstate starts tracking after Init in the pagelifecycle. You are probably binding too early for the objects to be stored in viewstate. You should bind the list view after the Init stage, probably in the load stage.
i.e.
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs)
ListView.DataSource = ProductLineChargeDetails
ListView.DataBind
End Sub

Databinding problems with postback

I've two issues currently preventing me from finishing two projects properly. I'll put them both here as I believe they're connected to the asp.net page lifecycle, but I can't find a way around them.
First I have a DropDownList which I must sort in codebehind. It only contains text, so I should be able to do that with the following method called in page load:
Dim alist As ArrayList = New ArrayList
For Each litem As ListItem In ltEsittelyDropDownList.Items
alist.Add(litem.Text)
Next
alist.Sort()
Dim uusiDDList As New DropDownList
For i As Integer = 0 To alist.Count - 1
Dim litem As New ListItem
litem.Text = alist(i).ToString
litem.Value = alist(i).ToString
uusiDDList.Items.Add(litem)
' Response.Write(alist(i).ToString)
Next
ltEsittelyDropDownList = uusiDDList
ltEsittelyDropDownList.DataBind()
As you can see, there's a commented response.write in there, which shows the list is actually sorted. So why, when I load the page, can't I see any effect?
The other problem, which is more critical and difficult, is as follows:
In the aspx page I'm binding a SQL Server 2005 datasource to a gridview. And in the code-behind I catch on to the RowDataBound event in which I handle some links and properties inside the gridviews' cells. But I cannot get this to work on the first page load, only after the first extra postback.
So, what is there to do? And thanks for all advice in front!
Your first problem is calling DataBind on a control you have filled manually. You likely have a DataSource specified in the control declaration, which is being used when DataBind is called. You can simplify the code by just adding the list items to the original control:
For i As Integer = 0 To alist.Count - 1
ltEsittelyDropDownList.Items.Add(New ListItem(alist(i).ToString())
Next
Alternatively, as you have a collection already, you can just bind it to the control:
ltEsittelyDropDownList.DataSource = alist
ltEsittelyDropDownList.DataBind()
For your second problem, some example code would help - specifically, where and how the control is databound and the code in RowDataBound.

Dealing with Databinding Errors in ASP.net

This has been bugging me for a while but when I databind a control using with a Session variable as the parameter which has not been initialized there is an exception thrown which I can't seem to catch anywhere.
Ideally if the session varaible is not set I would just like to redirect but I can't seem to figure out where I need to check for this instance.
You must check the session object on page_init event.
Check it in your page load.
Sub Page_Load()
if Not Page.ispostback()
if session("Value") <>"" then
me.hiddenfield.value = Session("ValueName")
Else
Response.redirect("PAge.aspx")
End if
End if
End Sub
I tend to add some hidden fields as sessions eventually time out
then make the datasource use the hidden control for its reference

Why is the DataBind() method necessary?

Simple question, I guess.
For a long time I've blindly followed a (supposedly) common pattern when programmatically databinding my ASP.NET controls. Namely:
gridView1.DataSource = someList;
gridView1.DataBind();
However, if I were setting my GridView to bind to a DataSource control via the DataSourceID property, the call to DataBind() is unnecessary. Namely:
gridView1.DataSourceID = LinqDataSource1;
is sufficient.
Furthermore, if you try to set the DataSource property in ASPX markup, you are greeted with the following:
You cannot set the DataSource property declaratively.
I assume these are related, but I am still stumped as to why DataBind() is necessary. The difference between DataSource and DataSourceID is secondary - I can understand some magic taking place there. The real question is why doesn't the DataSource propery setter cause databinding automatically? Are there any scenarios in which we want to set the DataSource but not bind to it?
In ASP.Net, it's often important to have certain data available and ready at certain points in the page life cycle, and not before. For example, you may need to bind to a drop down list early to allow setting the selected index on that list later. Or you might want to wait a bit to bind that large grid to reduce the amount of time you hold that connection active/keep the data in memory.
Having you explicitly call the .DataBind() method makes it possible to support scenarios at both ends of the spectrum.
DataSource is a property of the BaseDataBoundControl class. DataSourceID is a property of the DataBoundControl class, which inherits from BaseDataBoundControl and did not exist before ASP.NET 2.0.
Since DataBoundControl is explicitly for displaying data in a list or tabular form, and BaseDataBoundControl cannot make that assumption, binding is not automatic when DataSource is set because the type of control may not match the structure of the data.
Of course, this is all just a guess based on the MSDN documentation, so I could be wrong.
I noticed that without using DataBind() that nothing will be displayed in my GridView so I always include it as shown in this section of code:
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
' TableAdapter object.
' Provide communication between this application and the database.
'-----------------------------------------------------------------
Dim suppliersAdapter As New SuppliersTableAdapter
' Get the data from the TableAdapter into the GridView.
'------------------------------------------------------
GridView1.DataSource = suppliersAdapter.GetSuppliers()
' Display the result set from the TableAdapter in the GridView.
'--------------------------------------------------------------
GridView1.DataBind()
End Sub
Please forgive the extra commenting as I'm also still learning ASP.Net as well and the comments will help me learn better "what and why" to use certain statements.
Try this:
if (GridView1.EditIndex == e.Row.RowIndex)
{
TextBox t2 = (TextBox)e.Row.FindControl("TextBox2");
DateTime dt2;
if (DateTime.TryParse(t2.Text, out dt2))
{
t2.Text = dt2.ToString("yyyy-MM-dd");
}
}

Resources