Can I store a Scripting Dictionary in a session variable? - asp-classic

I have a classic ASP site where I create a dictionary when the user logs in and then stores that dictionary in a session variable like so...
dim objDict
set objDict = server.createobject("scripting.dictionary")
' processing here to fill dictionary
set session("user") = objDict
That all works fine and dandy but when I navigate to another page and try to access a value from the stored dictionary like this...
session("user").item("id")
I get the following error...
error '80020009'
Can anyone tell me if I'm accessing the stored dictionary incorrectly? Is storing the dictionary object in a session variable a bad/wrong thing to do?
Thanks

The error you are getting is a remote procedure call error. I can't explain why you get this error or why extracting into a local variable fixes it.
However I can tell that its really bad idea. When you store an object such as this in the Session object you create an affiliation between the current thread executing the script and the session. As result all subsequent requests for that session must now be handle only by this specific thread. If that thread happens to be busy handling someone elses request the sessions request is queued even if there are plenty of available work threads that could be used. Hence storing an object in the Session can significantly damage the scalability of the app.
I'd also question the wisdom of storing a Dictionary in something which is already a dictionary?
Why not just user :-
Dim userID : userID = Session("user_id")
Where anything you would normally have stored in the "user" dictionary, such as "id", would simply have the prefix "user_" and stored direcly in the Session?

Have you tried in your code doing something like the following when you want to access it?
Dim objDict
Set objDict = session("user")
Response.Write objDict("id")
Try that and see if it works. I wouldn't think this would be necessary but Classic ASP wasn't exactly the most robust language. What you want to do should be workable, since the Session object simply stores objects.

Related

Can I modify ASP.NET session object this way?

Imagine that I have an instance (oEmp) of "Employee" class and I would like to store it session.
Session["CurrentEmp"] = oEmp;
If I modify a property in oEmp as follows:
oEmp.Ename = "Scott";
Am I referring to session item through above statement or just only "oEmp"?
Session["CurrentEmp"] = oEmp; //Do we still need this after any property is modified
Is that the same case, if I opted for SQL Server session state (instead of InProc).
thanks
Asp.net Session will hold the reference, so you shouldn't need to do the following:
Session["CurrentEmp"] = oEmp;
after modifying oEmp;
Session Variables are held as reference types so there is no need to update its value every time.
You object instance that you store, only the reference to that object is stored in the session variable.
Here are some link to help you find more details
http://bytes.com/topic/asp-net/answers/447055-reference-types-session
http://forums.asp.net/t/350036.aspx/1
Do asp.net application variables pass by reference or value?
I am updating my response as my understanding of session data serialisation was not correct. I am not going to delete this answer as it might help other understand how session works. I would thank #Guru for point this out.
Irrespective of session mode, session data is updated back to session object only when the request is successful. So if you have assigned a reference object to session and then update the object in the same request, the session will hold the updated information.
Refer: Underpinnings of the Session State Implementation in ASP.NET for more information

Suitable method to implement caching in asp.net

I need to implement caching in Asp.net web application
My need to store data with different ID's.
So which method is better ?
Use a dictionary variable. Insert the data (key as ID and data as value).
Dim mDict As New Dictionary(Of String, String)
mDict .Add(bID, uwtTree.WriteXmlString(True, True))
Cache.Insert("mTree", mDict)
Add it to a cache variable.
Access the cache variable
If Not Cache("mTree") is Nothing Then
'cast to dictionary and check ID exists , if exsitis get the data
End iF
Use cache variable for different IDs
Cache.Insert(ID,data)
' each insertion for each ID
If Not Cache(ID) is Nothing Then
' get the data. '
End IF
Which method is the best way ? Or is there any other method exists ?
I am using .Net 3.5 /IIS 7 (VB.Net). Thanks in advance
Way to Improve Performance and Memory Optimization
Without context it's not possible to say which is "better".
But if you put a dictionary in the cache (option 1), better make sure it's a thread-safe dictionary (such as the .NET 4 ConcurrentDictionary).
The most obvious difference between the two approaches is that with option 1, cache item expiry will result in the dictionary with all items being removed at once. With option 2, individual items will expire independently.
In response to the comment:
i am having xml data and i will store in cache (data caching) as string. Is there any difference if i store it as XmlObject ?
The main differences I see are:
String is immutable, so you won't need to worry about thread-safety when accessing it. XML objects are not immutable - so you need to make sure you don't modify the object retrieved from the cache (or use locks to make sure any such modification is thread-safe).
If you store a string, you will presumably parse it into an XML object each time you retrieve it from the cache, which will result in a potential performance penalty.

Storing an array of dictionary objects in an application() object - Classic ASP

Using Classic ASP, does anyone know if it is possible (or advisable) to put an array of dictionary objects into an Application Object? I tried but after about 50,000 or so hits to the script below the App Pool gets corrupted or something and "trappable" C0000005 errors get generated when this line is run: dictLanguage=Application("lang")
Works fine for a few days though. Is it something to do with the way I've assigned the application object to another variable, I thought it would pass a pointer not a copy? Anyone smarter than me know what's going on here?
if isempty(Application("lang")) then
''# called when first visitor hits the page (following server reboot or app pool recycle)
init()
dictLanguage=Application("lang")
else
''# called for all other page hits
dictLanguage=Application("lang") ''# ***** TRAPPABLE ERROR after a few thousand page views *******
end if
''# // fill the application object with an array containing 10 dictionary objects, each holding a different language.
''# // This function appears to run just fine.
function init
Set initcn = Server.CreateObject("ADODB.Connection")
initcn.Open dbConStr
strSQL = "SELECT languageNo,quickRef,text FROM tblTranslation"
Set rs = initcn.Execute(strSQL)
dim d(10)
Set d(1)=Server.CreateObject("Scripting.Dictionary")
Set d(2)=Server.CreateObject("Scripting.Dictionary")
Set d(3)=Server.CreateObject("Scripting.Dictionary")
Set d(4)=Server.CreateObject("Scripting.Dictionary")
Set d(5)=Server.CreateObject("Scripting.Dictionary")
Set d(6)=Server.CreateObject("Scripting.Dictionary")
Set d(7)=Server.CreateObject("Scripting.Dictionary")
Set d(8)=Server.CreateObject("Scripting.Dictionary")
Set d(9)=Server.CreateObject("Scripting.Dictionary")
Set d(10)=Server.CreateObject("Scripting.Dictionary")
while not rs.eof
a=rs("languageNo")
b=rs("quickRef")
c=rs("text")
''# on error resume next
d(a).Add b,c
rs.movenext
wend
initcn.close
''# Storing the array in the Application object
Application.Lock
Application("lang") = d
Application.Unlock
end function
One object that is readily available and supports being stored in the application object has the ProgID "MSXML2.FreeThreadedDOMDocument.3.0"
This is a pretty good solution to loading fairly static application wide data, if you are using SQL Server then SQL Servers FOR XML feature makes it fairly easy to build some XML to load into the DOM.
You should not use Dictionary objects as application-level variables due to their threading model. If you need to use a Dictionary-like object in the application-level use the free Lookup Component from Microsoft (or a free Dictionary Component from Caprock Consulting).
To learn more, please visit this url.

Type Safe Coding

When i started developing web applications i stored the authentication details of the user in two session variables
Session["UserName"]="username";
Session["Password"]="paswword-123";
But someone proposed me an idea to create a class which holds the UserName and Password properties and on succesful authentication i have been asked to create an instance of the class and set the UserName and Password properties and store that instance in the session.
I have been told that the session object is TypeSafe. Can someone explain what is typesafe coding and the advantage of storing the object in the session.
Basically, the classic approach of storing values directly in Session["something"] has two drawbacks:
Magic strings: If you mistype something, your code compiles fine but you get either a runtime error or, worse, an unnoticed bug in your code.
Casting: After reading Session["something"], you need to cast it to the type you need. (This is what is meant by "not type-safe".)
Using a strongly-typed object that is stored in the Session eliminated the second problem. Well, actually, your custom object still needs to be cast, but it's only one cast instead of two (or ten) casts, which reduces the likelyhood of something going wrong. Again, a wrong cast is something which is only detected at run-time.
Another approach is to encapsulate the access to Session variables in static properties:
public class MySession {
public static string UserName {
get { return (string)HttpContext.Current.Session["UserName"]; }
set { HttpContext.Current.Session["UserName"] = value; }
}
}
Of course, both approaches can be combined, allowing you to group related properties (UserName and Password) in a common object.
Having a User class with 2 fields can be good for many reasons, as for type safety, if you ever type Session["Pasword"] somewhere you will get an error that wont be so easy to find, you will have to check for both parameter names everywhere. You need them to be correct, and its a great source of errors. Once you store User object instead of 2 unconnected strings you will have be able to use type safe code like User.Password instead of trying to access password by string indexer in Session. Also if your user ever gets more fields , which is very common you will simply add them to User class, not start creating new parameters & names and store them in Session heap.
As for typesafe coding I think http://en.wikipedia.org/wiki/Type_safety should help, or any other type of article on topic which is very popular I think.
Also I dont think you should store password in session, depends on your program logic but usually password should only be used to compute its md5 hash and never be used afterwards.
Well you're friend is half right, but I don't believe Session is inherently type safe. The Session collection stores instances of Object. So you can store an instance of any type (a string, an int, or a custom login class) because they all derive from object. However, when you retrieve that object, you don't know what type it is, and need to carefully cast it, with exception handling, before you use it.
eg this works fine:
Session["UserName"] = "Freddy";
string theUserName = (string)Session["UserName"];
However you could try to do the following, which will cause errors.
Session["UserName"] new StrangeDataClass(); //Uh Oh, that's not a string.
string theUserName = (string)Session["UserName"]; //unexpected behaviour based on StrangeDataClass.ToString() implementation.
To work around this, you'd have to do the following:
string theUserName = Session["UserName"] as string;
if (string != null)
//The cast worked...
else
//The cast failed, (or the string stored in session was null)
Having a custom login object slightly solves this problem, because you'd only have one object to worry about, and one cast to make. You could also extend the login object easily with extra information, and still not have to do any more casts.

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