NullReference on object in code behind after AsyncPostBack using UpdatePanel - asp.net

I'm building a simple game in an ASP.NET/VB.NET web app. The game has a UI made up of several ImageButtons.
The web page's code behind file holds an instance to the game object which will manage each turn taken by players.
Everything worked when the Game object's methods were Shared.
The problem occurred after refactoring to make the game object work as an instance instead of a shared class. Now, when the action returns to the code behind, the instance of the game object is nothing.
I suspect that this has something to do with view state, but uh... Google hasn't helped.
The code bits:
Public Class _Default
Inherits System.Web.UI.Page
Private _gamePanel As Panel
Private _updatePanel as UpdatePanel
Private _game as Game
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
If Not Page.IsPostBack Then
'create a new instance of the game object on first loading page
_game = New Game(width, height, cellsToWin)
End If
' DisplayGameBoard() does the following:
' * Add images to the GameBoard panel inside of the GameBoardUpdatePanel
' * Attach click event handler to each image (addressOf located in this
' code behind file
' * DisplayGameBoard() works fine the first time but fails on
' subsequent post backs because there is no game object instance
Me.DisplayGameBoard()
End Sub
(From the page directive)
Language="vb"
AutoEventWireup="false"
CodeBehind="Default.aspx.vb"
Inherits="Game._Default"
ValidateRequest="false"
EnableEventValidation="false"
EnableViewState="true"
(update panel on the web page)
<asp:UpdatePanel ID="GameBoardUpdatePanel"
runat="server"
UpdateMode="Conditional"
RenderMode="Block"
EnableViewState="true"
ViewStateMode="Enabled"
ChildrenAsTriggers="true" >
<ContentTemplate>
<asp:Label ID="PlayerName"
runat="server"></asp:Label>
<asp:Panel ID="GameBoard"
runat="server"
cssclass="gameBoard"></asp:Panel>
</ContentTemplate>
</asp:UpdatePanel>

It's not ViewState, it's simply the life time of the _Default instance.
You create an instance of the Game class and store that as a member of the page, and expect that instance to survive. The problem is that the instance of the page doesn't survive.
Each request for the page will result in a new instance of the _Default class to be created, and when the response is created the instance is thrown away. The reference to the instance of the Game class that you have stored in the page is also thrown away, and you lose any way to access it.
If you want to keep the instance of the Game class, you can store it in the Session collection, which is user specific:
If Not Page.IsPostBack Then
'create a new instance of the game object on first loading page
_game = New Game(width, height, cellsToWin)
' store the reference in the user session
Session("game") = _game
Else
' get the reference back from the user session
_game = DirectCast(Session("game"), Game)
End If
However, you should be carful about how much you store in the user session. Normally objects that are created in a page are short lived (i.e. milliseconds), so they have a small impact on the server resources. Anything that you store in the user session will be extremely long lived in comparison. Consider how large the Game object is, and if you really need to keep the entire object, or if you can keep just the information needed to recreate it for each request.

Related

How do I separate data in browser sessions when my form is using a gridview in asp.net

I am not sure if my question is clear but let me explain. First off I am not a professional programmer and I know that I am doing things wrong. I have created a form to collect contact information which has a gridview. My page does a few postbacks while I am collecting info. When there is one user and one browser everything works great. When I have multiple users trying to input data my app doesn't work as I want. So the first problem which I know is a problem is that I am using some static variables to store info. Point me in the direction I need to go in order to program correctly. The second issue and probably related; the gridview will display all information across browsers. Meaning if user A inputs info it will show in the gridview of user B across the globe. How do I make each person to have their own instance of the app? This is my real question. Any resources or guidance would be greatly appreciated.
Well I was trying to localize my variables so that they are not static but I am not sure if this is the only problem. I am thinking about viewstate but I am just not sure.
As you found out, using static variables will not work. Not only will it mess up your code with other users, those static variables "sort of" and "hope on a wing and prayer" will persist, but sometimes they do not!!!
so, static varabiles not only don't work with multi-user, but they can un-expected go out of scope.
The only use of a static class in .net and webforms?
For a shared code module. So in vb.net that means my "genreal" set and hodge podge of routines, or in c# we use a static class, and this is 100% fine as long as you NEVER use class varibles in those static classes (or code modules in vb.net), then you are fine.
So, for all kinds of general shared code, then a vb code module, or a c# static class is 100% fine. (Those routines can be passed values, and have local vars, but NEVER class scoped variables (since you never have to create an instance of such classes, then take off variables scoped to that given class, and you are just fine.
Ok, next up, what about persisting values in each page?
Well, first up TOP GOAL is to reduce the amount of persisting you have, and only persist things that you absolute must.
Now, most controls, including the grid view have automatic persistance built in.
However, this introduces our first design rule and pattern:
In the web form's page load, you are 100% free to load up the gridview, listbox(s), combo boxes (dropdown list) etc., but you ONLY want to load these things up ONE time and ONLY on first page load.
That means and results in the last 200 web pages I have built, that EVERY page has a "real" first load code stub.
That means this:
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
If Not IsPostBack Then
LoadGrid()
End If
End Sub
Sub LoadGrid()
Dim strSQL = "SELECT * FROM tblHotelsA ORDER BY HotelName"
GridView1.DataSource = Myrst(strSQL)
GridView1.DataBind()
End Sub
So, in above examle, I wanted to load up the GV with data.
Note the if not IsPostBack stub. EVERY one of my pages has such a code stub.
So, what this means is the page can survive post-backs, and a dropdown list etc will correctly save/persist their values.
However, so far, that does not cover variables and things say like a PK value.
So, the next two choices that are the bread and button choices are:
Session() - server side - per user.
ViewState() - client side browser - per web page.
Grasping when to use session() vs ViewState is quite much the Rossetta stone that un-locks how you can persist values, but not have values over write or clash with other web pages that a SINGLE user might have open.
So, I tend to follow this design pattern
Session() - use that to pass values to another page, but on the above first page load, transfer those values to ViewState().
Why?
Because session is global to teh one user.
lets take a typical + super simple example.
I have a list of hotels, user will click on a row - say to book a room in that hotel. When they select the one row from the GV, then we will jump to the next (differnt) web page that shows details about the hotel room etc.
So, I can use session() to PASS the hotel PK id to the next page, but I cannot use session() for that page to "operate" based on that PK session value.
Why?
Because what happens if they have two copies of the browser open, or have two tabs open on the list of hotels page, and then click on a row.
if they have two browsers (or 2 tabs - same case in regards to session(), then I can now have two web pages open on the hotel details/information.
But, if the code behind uses/assumes/operatates based on that session("HotelPK") value, then I have two pages open running that same code!!
Now, if you buying a house, you may well wind up buying the wrong house!!
Or you have two web pages open, code assumed each has HotelID, but session() is global to the one user.
So, session is great for persiting values global to the one user, but NOT for code that needs some values persited for the ONE page.
So, I will (and do) use sesison all the time to pass values to the next web page, but FIRST task is to then transfer that value to viewstate(), and hten all code on that page operates/uses/consumes ViewState to function.
So, say I have this GV of hotels:
<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False"
DataKeyNames="ID" Width="100%" CssClass="table table-hover" >
<Columns>
<asp:BoundField DataField="FirstName" HeaderText="First Name" />
<asp:BoundField DataField="LastName" HeaderText="Last Name" />
<asp:BoundField DataField="City" HeaderText="City" />
<asp:BoundField DataField="HotelName" HeaderText="Hotel Name" />
<asp:BoundField DataField="Description" HeaderText="Description" />
<asp:TemplateField>
<ItemTemplate>
<asp:Button ID="cmdEdit" runat="server" Text="Edit" CssClass="btn myshadow"
OnClick="cmdEdit_Click" />
</ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
Above page load code is used, and I now have this:
Ok, so now we click on that View button, and we will grab the PK value
(Which by the way is NEVER exposed to the client-side browser for reasons of security).
Now, when you hit view, we have to store/save/have use of/pass the Hotel PK id selected from above. So, we will "need" to persist that value in the code that operates on that ONE hotel we selected.
As noted, we have several options:
HiddenField - this actually uses viewstate, but is OFTEN handy for persisting values, and nice is client-side JavaScript (js) can use (or even set) such values, they persist and the values in a hidden field can be used by both client-side code, and server-side code. This of course is a "per page" persisting. This choice can't (should not) be used for things like row PK values, but for "low" security values and variables, a HiddenField can be a great choice.
The other way to persist values is to place/put them in the URL as paramaters. I think that looks ugly as can be, but worse, once again, this only works for values that of "low" value.
So, to pass a value to the next page, we can use several approaches, but session() is really good, per user, but ONCE we have that new page loaded, then we use ViewState.
So, the code for above "view" button can/will look like this:
Protected Sub cmdEdit_Click(sender As Object, e As EventArgs)
Dim btn As Button = sender
Dim gRow As GridViewRow = btn.NamingContainer
Dim HotelPK As Integer = GridView1.DataKeys(gRow.RowIndex).Item("ID")
' Now jump to our hotels edit page:
Session("HotelPK") = HotelPK
Response.Redirect("EditHotel")
End Sub
So, we can use session() to pass the HotelPK.
But, as noted, we ONLY use session for passing the value, NOT persisting.
So, now on the hotel edit page (hotel.aspx), then our all important load event will have this:
Dim intHotelPK As Integer = 0
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
If Not IsPostBack Then
intHotelPK = Session("HotelPK")
ViewState("HotelPK") = intHotelPK
' Load up our form to edit hotel
Dim strSQL As String = "SELECT * FROM tblHotelsA WHERE ID = " & intHotelPK
Dim rstHotel As DataTable = Myrst(strSQL).Rows(0)
Call fLoader(Me.EditHotelDiv, rstHotel)
Else
intHotelPK = ViewState("HotelPK")
End If
End Sub
Now, for any button click, any code that runs in this page?
Any variable, including any post-back button can use intHotelPK.
so, my save button code would/could be this:
Protected Sub cmdSave_ServerClick(sender As Object, e As EventArgs)
Dim strSQL As String = "SELECT * FROM tblHotelsA WHERE ID = " & intHotelPK
Dim rstData As DataTable = Myrst(strSQL)
Call fWriterW(EditRecord, rstData.Rows(0))
Call MyRstSave(rstData, "tblHotelsA")
Response.Redirect("ChooseHotel")
End Sub
Note how any button click, and any code now has a persisted value of HotelPK, and any code, post-back code etc. is now free to use that persisted value.
And EVEN if the user will have two browser tabs open, or two copies of the browser?
The code will still work fine, since they can't really click on two open web pages at the same time. So, we can (safe) pass the pk id, shove into session, and now that page will operate correctly, have a persisted "hotel PK id", and it will not matter even if they have 2 or 5 pages open (more then one time). the code will work, since ViewState is per page.
However, use caution with ViewState. ViewState is client side storage, and each button click (post-back) means that ViewState travels and makes the round trip. (thus increasing the size of the post back). So, don't go shoving in some large dataset or datatable - since it will cost dearly in terms of response time.
So hopefully the above gives you some ideas here.
This also means in some cases, you will "often" re-pull data from the database, and in 99% of cases, a re-pull of some data for code behind is BETTER than attempting to persist a dataset/datatable in code behind.
And in fact, unless only a few rows of data, I don't recommend using ViewState for persisting datasets/datatables. Session() is much better if you must do this, but while session() is 100% server side, it also per user, and not per page - so as noted, use session () for passing values to the next page.
And if you need to pass 5 variables to the next page? Build a little class, since then you can "group" sets of values required for a given page operation, and they also facilitate passing of values to the next page with great ease.

asp.net/vb.net System.Web.UI.WebControls.RepeaterItemEventArgs question

I'm really new to asp.net so please forgive me if this seems like a really basic question. I have an asp.net page that contains a repeater. Here's the code:
<div class="formRow">
<asp:Repeater ID="uxStudentFormActive" runat="server">
<ItemTemplate>
<span style="font-weight:bold;" ><asp:Literal ID="uxFormName" runat="server" Text="#" /></span><br />
<asp:TreeView ID="uxFormHistoryList" runat="server" Target="_blank" />
</ItemTemplate>
</asp:Repeater>
</div>
Here's the sub in my vb.net page that handles uxStudentFormActive.ItemDataBound:
Protected Sub uxStudentFormActive_ItemDataBound(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.RepeaterItemEventArgs) Handles uxStudentFormActive.ItemDataBound
Dim dr As DataRowView = CType(e.Item.DataItem(), DataRowView)
If Convert.ToInt32(dr("FormId")) = 29 Then
...
End If
End Sub
I'm not exactly sure how the aspx page interacts with the vb.net page. My question is how do I find out how where the values for e that are being passed to the sub uxStudentFormActive_ItemDataBound in my vb.net page are coming from? Thanks in advance.
From this MSDN article on handling events in ASP.Net:
Events [in ASP.Net] are based on the delegate model...A delegate is a type that holds a reference to a method... An event is a message sent by an object to signal the occurrence of an action. The action could be caused by user interaction, such as a button click, or it could be raised by some other program logic, such as changing a property’s value. The object that raises the event is called the event sender... Data that is associated with an event can be provided through an event data class.
In your case, the event data class is RepeaterItemEventArgs.
To respond to an event, you define an event handler method in the event receiver. This method must match the signature of the delegate for the event you are handling. In the event handler, you perform the actions that are required when the event is raised, such as collecting user input after the user clicks a button. To receive notifications when the event occurs, your event handler method must subscribe to the event.
Reading that, you might say "Well that's well and good, but what does it mean?" In your project, you probably have a property set at the top of your .aspx page named AutoEventWireup. It's probably set to true. This property does what it seems: it automatically wires up your events so that you don't have to. This is how your .aspx page knows how to interact with the code-behind file.
On your .aspx page, you have your repeater control. On your code-behind file, you have your event handler method. Because you have AutoEventWireup set to true, those two things are automatically linked together as long as your event handler method signature matches the signature of the delegate for that event. In this case, that event is ItemDataBound.
To your original question, where do the values of e come from? From the sender!
Protected Sub uxStudentFormActive_ItemDataBound(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.RepeaterItemEventArgs) Handles uxStudentFormActive.ItemDataBound
In this method signature, you have two parameters: sender, and e. As described in the quote above, the sender is the object that raises the event. In your case, this is the RepeaterItem. Since the repeater likely contains many of these objects, the event can be raised multiple times. The event argument, e, is generated from the sender, or the RepeaterItem that was databound and caused the event to fire.
You can read more about the RepeaterItemEventArgs and the data available within on the MSDN.
As a side note, you can set AutoEventWireup to false and manually wireup the events as described in depth in the link to the MSDN article on the AutoEventWireup property.
Thanks to #Jack for giving me more insight into this. I'm sorry if my OP wasn't more clear, I did understand that the values were coming from the .aspx page, what I was actually asking is where the values that are being passed as e into my sub are being set, how e is being populated with data. The answer came from looking at the repeater id for the repeater I'm asking about, uxStudentFormActive. When I searched for this repeater id my vb.net code behind I found that the data source for the it was defined and bound in the Page_Load sub. Tracking this down lead me to a stored procedure in my database that is being passed session data and e is being set to the results of the stored procedure.

VB.Net: call sub from shared sub

I have some Ajax on a web page that feeds some data to a server-side VB.Net method. Once that data is in the server-side method, I need to call another server-side method to use the data I just collected. Here is a really simplified example:
' This method gets the input from the Ajax code on the web page.
<System.Web.Services.WebMethod> _
Public Shared Sub GetAwesome(VBInputText As String)
Dim strTest As String = VBInputText
' Now that we have collected input from the user,
' we need to run a method that does a ton of other stuff.
DisplayAwesome(VBInputText)
End Sub
Protected Sub DisplayAwesome(AwesomeIn As String)
' The real app does a lot more than this. For this example, it
' just sets the text of a literal.
litAwesomeResult.Text = AwesomeIn
End Sub
Of course, in the above example DisplayAwesome(VBInputText) gives me the 'Cannot refer to an instance member...' error. So, is it possible now to call Protected Sub DisplayAwesome from Public Shared Sub GetAwesome? I'm hoping to stay close to this sort of solution because it would play very well with the app as it is already written by another coworker.
unfortunately you cannot do this, Since the page method DisplayAwesome is defined as Protected and you requires an instance of the class to access the Protected method. But changes in another instance will not reflect in the current UI. another thing you can do is Make DisplayAwesome as Shared, but this time you cannot access the UI elements inside the shared function.
The thing you can do in this situation is, return data to the called method(in front end) and handle the litAwesomeResult.Text there
Call sub with name of Form Class like this:
FormName.DisplayAwesome(VBInputText)
In VB.Net, you can call the method not shared from a shared method with Name of Form Class by default instance, because The default instance is an object Form type that the VB application framework create and manage it, when the form is added to the project.
For more info see this :
VB.NET Default Form Instances

Why is my session variable being shared by multiple users in asp.net application

I have a session variable with a datatable assigned to it. For some reason the results from the datatable (display to user in a GridView) are being shared accross multiple users who are logged in. I thought each session was independent? So when one user makes changes then the other users see those results added to their results. Not sure why. I am not using Application variables.
I initialize the Session variable in Global_asax, then populate it on a button command after the user has filled out the required entries.
Imports System.Web.SessionState
Public Class Global_asax
Inherits System.Web.HttpApplication
Sub Session_Start(ByVal sender As Object, ByVal e As EventArgs)
Session("RDDT") = New DataTable
End Sub
End Class
As it stands, I did overlook something. There was a usercontrol that someone else had created that had Public variables. The application was treating them as static. All I did was remove them and changed where in use to Session varibles. The Application works as intended now.

Accessing public class variables in asp.net without session

I am using this example I found to learn how to load class files and access variables through them. This is in a file called Class1.vb in the App_Code folder (this is not an app project):
Imports Microsoft.VisualBasic
Public Class my_class
Public Shared Sub my_sub()
Dim vartest As String
vartest = 10
HttpContext.Current.Session("myvar") = vartest
End Sub
End Class
This is the codebehind on the aspx file:
Imports my_class
Partial Public Class test
Inherits System.Web.UI.Page
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
my_class.my_sub()
Label1.Text = HttpContext.Current.Session("myvar")
End Sub
End Class
How could I access the vartest variable without using a session, since if this is accessed by multiple functions at the same time the variable can be overwritten I assume. Is it possible to go the other way, where a variable is sent to a class file?
It sounds like you need a quick overview of some basic ASP.Net Webforms concepts. Up first I'll counter a common newbie misconception:
Your Page class does not hang around on the web server for very long
I think many new ASP.Net developers have this idea of the web server keeping a single instance of their page class for every user session that hits their site, and each postback or event uses this same page class instance. That's just not how it works. ASP.Net page class instances are nearly always created and destroyed again in well under a second, and most experienced developers see it as a big problem if it takes longer.
ASP.NET relies on the HTTP protocol
The thing to remember here is ASP.Net still relies on the HTTP protocol, and http boils down to requests and responses. When you view a web page, your browser first sends a request to a server. The server responds, usually with an html document. The browser will then parse the html; based on what it sees in the html the browser may send more requests to the server for additional resources, such as javascript, images, or css files. Each request results in a separate response, and the browser uses all these resources to render the page to the screen. However, the ASP.Net runtime normally does not have to process the additional requests (that would make things slower) — ony the initial html needs ASP.Net support; you want the other resources to be basic files that can be cached.
The ASP.Net runtime creates a new instance of your class for every request.
When the ASP.net runtime processes a request for a page, it will create a new instance of your page class. The runtime will follow the ASP.Net Page lifecycle (this should really be named the "ASP.Net Page Request Lifecycle"), and call certain methods or raise certain events in this class instance, in a specific order defined by the lifecycle.
This means every postback or event runs in a different instance of your class.
It also means every postback or event is rebuilding and transmitting all of the html the goes into your page, and not just the portions you want to change. For your server code, the consequence is the only thing class-level variables are really good for in ASP.Net is things that will be used within a single http request. For the browser, the consequence is you're working with a brand new DOM after every event.
To understand all of that, it's important here to also have a good understanding of the difference between a class and an instance of a class. A couple items in your question make me unsure whether you have this understanding yet.
The ASP.Net runtime shares one application instance among all users of your site
The web server typically only has one instance of your application for the entire web site and all it's users. Therefore, anything with a Shared/static scope is common to every user. It's rarely appropriate in ASP.Net for anything to be Shared/static.
So how do you handle data that should live with a single user or visit to your site?
This is exactly what the Session is for. A session will always be unique to an individual request at any given time. You're worried about multiple functions accessing the session at the same time, but this does not happen. The ASP.Net Page Lifecycle ensures that unless you manually spawn additional threads, only one function at a time is running for a given HttpContext and Session. If a user somehow sends two requests at about the same time that should have the same Session/HttpContext, one will be held by the ASP.Net runtime until the other is completed. If you don't want to reference the session all the time, you can build properties in your class that wrap session variables. See #Pankaj's answer for an example.
First, a Session has user-scope, so it will not be overwritten by another Request.
Is it safe to access asp.net session variables through static properties of a static object?
You could encapsulate the access into a property:
Public Shared Property MyVar() As String
Get
If HttpContext.Current.Session("MyVar") Is Nothing Then
HttpContext.Current.Session("MyVar") = ""
End If
Return DirectCast(HttpContext.Current.Session("MyVar"), String)
End Get
Set(value As String)
HttpContext.Current.Session("MyVar") = value
End Set
End Property
Then you can get the variable by:
Label1.Text = my_class.MyVar
In addition to the "Tim Schmelter" reply....
You can create a BaseClass which will inherit from
System.Web.UI.Page
Place the property as suggested by "Tim". The only change you need to do is to change the access modifier to Protected and you should remove Public and Shared
You can also keep other common functions, properties that can we reused in other classes also... Similarly you can create BaseControls as well for your User controls
Finally, inherit this class in the web form....
Hope this will help you...
Base Class code
Imports System
Imports System.Collections.Generic
Imports System.Linq
Imports System.Web
Public Class BaseClass
Inherits System.Web.UI.Page
Protected Property MyVar() As String
Get
If HttpContext.Current.Session("MyVar") Is Nothing Then
HttpContext.Current.Session("MyVar") = ""
End If
Return Convert.ToString(HttpContext.Current.Session("MyVar"))
End Get
Set
HttpContext.Current.Session("MyVar") = value
End Set
End Property
End Class
Sample Code "Behind Code" - Showing the usage of Protected member Data from Base Class
Imports System
Imports System.Collections.Generic
Imports System.Linq
Imports System.Web
Imports System.Web.UI
Imports System.Web.UI.WebControls
Public Partial Class Default5
Inherits BaseClass
Protected Sub Page_Load(sender As Object, e As EventArgs)
If Not Page.IsPostBack Then
Dim str As String = Me.MyVar
End If
End Sub
End Class
Generally you can use different places to store application state: Application (application wide, saves state into application domain), Session (there can be saved everything what will be accessed by current browser session), ViewState (variables stored in hidden input field and will be posted on every postback). Of course you can also save state to database or file. I'm not sure what you want to achieve, but looks like you looking for something like ViewState.
Read ASP.NET State Management

Resources