How can i preserve list contents on postbacks? - asp.net

On the page load event of my webpage i fill the list of with the contents of the structure
Structure MainStruct
Dim Ans1 As String
Dim Ans2 As String
End Structure
Dim Build As New List(Of MainStruct)
The problem i that on post-back the contents of the list-of get lost.
So, how can i preserve the contents of the list-of in ASP.NET?

You can either put it in a session or a viewstate in case you donot wish to keep in a storage like database or a ASP.net cache. Of course, you shall have to be careful if you keep it in session as you shall have to clean it up as soon as you go out of scope. Also, serialising/deserialising will be another angle. Can't you have some global cache or so for your app?

Related

VB .NET Textbox Shallow Copy into Dictionary

While there are many questions about shallow copy vs. deep copy, I couldn't find one specific to TextBoxes and Dictionaries in VB .NET. I believe my problem is unique enough to deserve it's own thread.
In VB .NET I am trying to implement a dictionary that links an internal class called PageDisplayFields.DisplayField that is essentially an Enum to TextBoxes. The key is the Enum and the value is the System.Web.UI.WebControls.TextBox. My issue is that I'd like to then be able to change the .Text of the TextBox in the dictionary and have it also change the .Text property of the TextBox originally input into the Dictionary. When the TextBox is added to the dictionary, it appears to be a deep copy, and not a shallow copy. Changes to the .Text property of either the original TextBox or the copy in the Dictionary do not pass through to one another.
Here's a code sample of what I'm trying to do:
Private Shared _TxtBoxLookup As New Dictionary(Of PageDisplayFields.DisplayFields, System.Web.UI.WebControls.TextBox)
Private Sub SetTxtBoxLookup()
_TxtBoxLookup.Add(PageDisplayFields.DisplayFields.A, txtA)
_TxtBoxLookup.Add(PageDisplayFields.DisplayFields.B, txtB)
_TxtBoxLookup.Add(PageDisplayFields.DisplayFields.C, txtC)
_TxtBoxLookup.Add(PageDisplayFields.DisplayFields.D, txtD)
_TxtBoxLookup.Add(PageDisplayFields.DisplayFields.E, txtE)
End Sub
SetTxtBoxLookup() is called at the correct point only once, and doesn't have any errors. The structure of the _TextBoxLookup is never modified after this call.
If _TxtBoxLookup.ContainsKey(PageDisplayFields.DisplayFields.A) Then
_TxtBoxLookup.Item(PageDisplayFields.DisplayFields.A).Text = "ThisText"
End If
If this code is called, _TxtBoxLookup.Item(PageDisplayFields.DisplayFields.A).Text is actually set to "ThisText". However, txtA.Text = "" (the default value).
Similarly, if I do the reverse operation, the same effect is observed:
ModHashTable("A") = _TxtBoxLookup.Item(PageDIsplayFields.DisplayFields.A).Text
'Assume "A" is the correct key to ModHashTable
If "3" is entered into the TextBox, txtA, ModHashTable("A") will be set to "" as this is still the value that _TxtBoxLookup.Item(PageDisplayFields.DisplayFields.A).Text has stored.
If I implement storage and retrieval by directly referencing the TextBoxes, everything works as expected, so I'm 99% sure that the problem in my code is that the TextBoxes aren't shallow copying, but are deep copying. Using dictionaries in this way let's me drastically reduce the length of many methods.
Is there a way to make the Dictionary store only references to the TextBoxes? An easy way to do a shallow copy?
This is ingenious, but it probably isn't going to work as written, because of how ASP.NET web pages work.
Every time a page loads, all the controls on it are recreated and repopulated from ViewState. So, after postback, you may still have a shared Dictionary of controls ... but they are not the control objects that are currently on the page. They are the old ones.
Now, you may still be able to make this work, as long as you re-load your dictionary with the controls at the beginning of the page lifecycle, such as in Page_Load.
I would avoid using a shared Dictionary in any case, because it's going to be shared across all instances of the page. So two users accessing the same page will share the same dictionary, with potentially disastrous results.

Share data between aspx pages

I need to share data (string, list, array) between two different aspx pages of the same application. What is the best way to do it if I do not want to use cookies and do not want for data to be visible in url.
a) Form post method
b) Session (cookies?)
c) Sql
d) Server.Transfer
Thanks
In-memory Session will be the simplest and quickest (development-wise) to store data between pages without their contents being visible in the query string (URL), like this:
To store a List<string> in Session, do this:
var listOfStrings = new List<string>();
listOfStrings.Add("1");
listOfStrings.Add("2");
listOfStrings.Add("3");
Session["ListOfStrings"] = listOfStrings;
To retrieve the List<string> from Session, do this:
// Check to see if item in Session is actually there or not
if(Session["ListOfStrings"] != null)
{
// Cast the item in Session to a List<T>, because everything in Session is an object
var myListOfStringsRetrieved = Session["ListOfStrings"] as List<string>;
}
Note: I am assuming you use C#, but this can easily be translated to VB.NET.
Some more details might be helpful. What type of information do you want to share? If it is something that needs to be saved, then perhaps it makes most sense to save the data in your database (or local storage, or what ever you are using) from one page and retrieve it in the other. If it's just temporary data, it probably makes more sense to post the data through a form, or use a session variable. The problem with the session variable is that you might time-out your session. A session variable wouldn't be my first choice.

Sql data reader in classes

Hi
On several pages of our website, I want to check if the currently logged in user has accepted our terms and conditions in the past. This boolean value is stored in the application database. Rather than creating a sql data reader afresh on each relevant page, I thought I could put it in a class and then assign the true/false to a variable. This is what I have so far and it does exactly what I want it to:
Public Shared ReadOnly Property termsCompleted As String
Get
Dim selectTerms As String = "SELECT Terms FROM tblPersonal WHERE Ref=#Ref"
Dim dbconn As String = ConfigurationManager.ConnectionStrings("ApplicationServices").ConnectionString
Using myConnection As New SqlConnection(dbconn)
myConnection.Open()
Dim cmdTerms As New SqlCommand(selectTerms, myConnection)
cmdTerms.Parameters.AddWithValue("#Ref", myUser.ToString())
Dim readerTerms As SqlDataReader = cmdTerms.ExecuteReader()
readerTerms.Read()
termsCompleted = readerTerms.Item(0)
readerTerms.Close()
myConnection.Close()
End Using
End Get
End Property
I am them using the following on each page that is relevant to deny access and redirect (in the page_load):
If Variables.termsCompleted = False Then
Response.Redirect("Terms.aspx")
End If
While this works ok, i'm interested in how secure it is, and is there a better way to do this?
Thanks
Have you considered retrieving the information once during Session_Start, and carrying it around in Session so that you can interrogate it any time you want?
If you can't retrieve the data during authentication/authorization, you would retrieve the data in the same way as you show above.
To put the value into Session: Session["termsCompleted"] = "true";
To read the value from Session: if (Session["termsCompleted"] == "true")....
As an alternative, you could add the information to HttpContext.Current.User.
Sub Session_Start(ByVal sender As Object, ByVal e As EventArgs)
' Code that runs when a new session is started
Dim selectTerms As String = "SELECT Terms FROM tblPersonal WHERE Ref=#Ref"
If Request.IsAuthenticated = True Then
Dim dbconn As String = ConfigurationManager.ConnectionStrings("ApplicationServices").ConnectionString
Using myConnection As New SqlConnection(dbconn)
myConnection.Open()
Dim cmdTerms As New SqlCommand(selectTerms, myConnection)
cmdTerms.Parameters.AddWithValue("#Ref", Variables.myUser)
Dim readerTerms As SqlDataReader = cmdTerms.ExecuteReader()
readerTerms.Read()
Session("termsCompleted") = readerTerms.Item(0)
readerTerms.Close()
myConnection.Close()
End Using
End If
End Sub
And in the code-behind:
If Session("termsCompleted") = False Then
Response.Redirect("Terms.aspx")
End If
Unfortunately this is redirecting to the terms.aspx page every time regardless of what is in the database. From debugging it's picking up the reader item as 'False' even when it's true..
Thanks
Create a base page and have each page inherit from that. In this base page you can do the data access once to perform this check. Then store it in session state.
I don't think you have a security issue...I think it's more of a best practice issue. It's not good practice to put your data access requests in a property. In projects I work on, I typically will have a class that has functions that handle my data access with a buisiness layer that makes the calls to my data access. An n-tier project design may not fit your project...I'm just speaking from my experience.
If you need to reuse the bit flag, just store it in Session.
This logic doesn't really belong on a page. If accepting the terms of use is a requirement for accessing parts of your site then you should handle it that way. This problem is a very similar situation to having an administrator section of a site that only a few users can access.
Ideally this is something you would handle before the request gets to the page. There are (at least) two ways to approach this.
You could write a custom HTTP module that subscribes to the AuthorizeRequest event. It will check whether this page requires that you accept terms of agreement or not and if so checks to see if the user has or not. If not it will redirect the user to the terms of use page.
The other option is to put this code into your Global.ascx file. You would want to subscribe to the AuthorizeRequest event and perform your logic there.
I don't think it matters which option you chose (though the second one may be a little more straight forward to implement). The benefit is that this concern is handled outside of the page itself. This way as you add new pages to your site, you can't forget to add your validation code to it. If you ever decide that in addition to accepting terms of agreement users need to do something else, you now have one place to change instead of going through all of the pages, etc.
You should also take advice of some of the other answers and store this value into the Session to avoid having to to do a database request every time a page loads.
More information:
Http Modules and handlers
How To Create an ASP.NET HTTP Module Using Visual C# .NET (VB should be the same concept with VB syntax).
Application, Page and Control lifecycle (to help you better understand how ASP.NET application lifecycle works and what else is possible).

Syntax to change the value of a cached object property

In an ASP.NET 3.5 VB web app, I successfully manage to cache an object containing several personal details such as name, address, etc. One of the items is CreditNum which I'd like to change in the cache on the fly. Is there a way to access this directly in the cache or do I have to destroy and rebuild the whole object just to change the value of objMemberDetails.CreditNum?
The cache is set using:
Public Shared Sub CacheSet(ByVal key As String, ByVal value As Object)
Dim userID As String = HttpContext.Current.User.Identity.Name
HttpContext.Current.Cache(key & "_" & userID) = value
End Sub
Besides that this answer might help; Cache is really there to help you add, read, and drop the objects that your app requires frequently.

Passing Objects via QueryString

I have object A which in turn has a property of type Object B
Class A
property x as Object B
End Class
On my ASP.NET page when I select a gridview item which maps to an object of type A I serialize the object onto the QueryString and pass it to the next page.
However I run into problems if property x actually has some value as it looks like I exceed the QueryString capacity length of 4k (although I didn't think the objects were that large)
I have already considered the following approaches to do this
Session Variables
Approach not used as I have read that this is bad practice.
Using a unique key for the object and retrieving it on the next page.
Approach not used as the objects do not map to a single instance in a table, they arte composed of data from different databases.
So I guess my question is two fold
Is it worth using GKZip to compress the querystring further (is this possible??)
What other methods would people suggest to do this?
If displaying the url of the next page in the browser does not matter, you could use the context.items collection.
context.items.add("keyA", objectA)
server.transfer("nextPage.aspx")
Then on the next page:
public sub page_load(...)
dim objectA as A = ctype(context.items("keyA"), objectA)
dim objectB as B = objectA.B
end sub
One reason to use this is if you want the users to believe that the next page is really a part of the first page. To them, it only appears as if a PostBack has occurred.
Also, you don't really need a unique key using this approach if the only way to use "next page" is if you first came from "first page". The scope for the context items collections is specific to just this particular request.
I agree with the other posters who mentioned that serialized objects on the querystring is a much worse evil than using session state. If you do use session state, just remember to clear the key you use immediately after using it.
I don't understand why you wouldn't use session state but...
Option 1: Viewstate
Option 2: Form parameters instead of querystring
But also be aware that you do not get the same object back when you serialize/deserialize. You get a new object initialized with the values of the original that were serialized out. You're going to end up with two of the object.
EDIT: You can store values in viewstate using the same syntax as Session state
ViewState["key"] = val;
The value has to be serializeable though.
While storing objects in session might be considered bad practice, it's lightyears better than passing them via serialized querystrings.
Back in classic asp, storing objects in session was considered bad practice because you created thread-affinity, and you also limited your ability to scale the site by adding other web servers. This is no longer a problem with asp.net (as long as you use an external stateserver).
There are other reasons to avoid session variables, but in your case I think that's the way to go.
Another option is to combine the 2 pages that need access to this object into one page, using panels to hide and display the needed "sub-pages" and use viewstate to store the object.
I don't think passing it in the query string, or storing it in the session, is a good idea.
You need one of the following:
a) A caching layer. Something like Microsoft Velocity would work, but I doubt you need something on that scale.
b) Put the keys to each object in the databases that you need in the query string and retrieve them the next time around. (E.g. myurl.com/mypage.aspx?db1objectkey=123&db2objectkey=345&db3objectkey=456)
Using session state seems like the most practical way to do this, its exactly what its designed for.
Cache is probably not the answer here either. As Telos mentioned, I'm not sure why you're not considering session.
If you have a page that depends on this data being available, then you just throw a guard clause in the page load...
public void Page_Load()
{
if(!IsPostBack)
{
const string key = "FunkyObject";
if(Session[key] == null)
Response.Redirect("firstStep.aspx");
var obj = (FunkyObject)Session[key];
DoSomething(obj);
}
}
If session is absolutely out of the quesiton, then you'll have to re-materialize this object on the other page. Just send the unique identifier in the querystring so you can pull it back again.
Session isn't always available. For instance when XSS (cross-site-scripting) security settings on IE prevent the storage of third-party cookies. If your site is being called within an IFrame from a site that's not your DNS domain, your cookies are going to be blocked by default. No cookies = no session.
Another example is where you have to pass control to another website that will make the callback to your site as a pure URL, not a post. In this case you have to store your session parameters in a querystring parameter, something that's tough to do given the 4k size constraint and URL encoding, not to mention encryption, etc.
The issue is that most of the built-in serialisation methods are pretty verbose, thus one has to resort to a roll-your-own method, probably using reflection.
Another reason for not using sessions is simply to give a better user experience; sessions get cleared after N minutes and when the server restarts. OK, in this case a viewstate is preferable, but sometimes it's not possible to use a form. OK, one could rely on JavaScript to do a postback, but again, that's not always possible.
These are the problems I'm currently coding around.
Here is what I do:
Page1.aspx - Add a public property of an instance of my object. Add a button (Button1) with the PostBackURL property set to ~/Page2.aspx
Private _RP as ReportParameters
Public ReadOnly Property ReportParams() as ReportParameters
Get
Return _RP
End Get
End Property
Protected Sub Button1_Click(ByVal sender As Object, ByVal e As EventArgs) Handles Button1.Click
_RP = New ReportParameters
_RP.Name = "Report 1"
_RP.Param = "42"
End Sub
Now, on the second page, Page2.aspx add the following to the Markup at the top of the page under the first directive:
<%# PreviousPageType VirtualPath="~/Default.aspx" %>
Then for the Page_Load in the code behind for Page2.aspx, add the following
If Not Page.PreviousPage is Nothing Then
Response.write (PreviousPage.ReportParams.Name & " " & PreviousPage.ReportParams.Param)
End If
Faced with a similar situation what I did, is to XML serialize the object and pass it around as query string parameter. The difficulty with this approach was that despite encoding, the receiving form throws exception saying "potentially dangerous request...". The way I got around was to encrypt the serialized object and then encode to pass it around as query string parameter. Which in turn made the query string tamper proof (bonus wandering into the HMAC territory)!
FormA XML serializes an object > encrypts the serialized string > encode > pass as query string to FormB FormB decrypts the query parameter value (as request.querystring decodes also) > deserialize the resulting XML string to object using XmlSerializer.
I can share my VB.NET code upon request to howIdidit-at-applecart-dot-net

Resources