Accessing public class variables in asp.net without session - asp.net

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

Related

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

What type of variable could I use for this?

What I am trying to do is simple in principle, but the lifecycle of ASP.NET pages is throwing a bucket of cold water into my day.
Here is the problem
We have implemented URL Redirection and I've inherited code that reads like this in Global.ASAX, on Sub Application_BeginRequest:
dv.Table = CommonFunctions.ConvertXmlFileToDataSet("/XmlData/WebAppFolders.xml").Tables("Application")
dv.RowFilter = "'" + fullOrigionalPath + "' LIKE '%'+ folder + '%'"
If dv.Count > 0 Then 'match on key to redirect
If fullOrigionalPath.EndsWith(dv(0)("folder") + "/") Then
Context.RewritePath(dv(0)("basePage"), True)
Else 'missing page in directory --> redirect
HttpContext.Current.Items("Raise404") = "true"
Response.Redirect("/" + dv(0)("folder"))
End If
Return
End If
Basically we are reading a large XML file that contains the URL redirects. That's working fine. The problem happens in the line...
HttpContext.Current.Items("Raise404") = "true"
In the context of Application_BeginRequest, the session object is not yet available, so I could not use it to store a flag which I named Raise404. Instead I had to resort to using the Items collection.
The problem occurs when the redirect takes place. The new page lifecycle destroys the Items array and overwrites it with a new empty one.
By the time I try to use my flag Raise404, it no longer exists on my page PreRender event.
To complicate matters we use master pages, and so I was asked to place the code that we want to execute in the master page
Ideally, if the Items array wasn't being destroyed, this code would work:
Private Sub Page_PreRender(sender As Object, e As System.EventArgs) Handles Me.PreRender
If HttpContext.Current.Items("Raise404") IsNot Nothing AndAlso HttpContext.Current.Items("Raise404").Equals("true") Then
Response.StatusCode = 404
HttpContext.Current.Items("Raise404") = Nothing
End If
End Sub
I am not sure what kind of variable I could use to store my flag and allow it to survive the redirect.
Any ideas?
Update: The problem is that the HTTP Handler servicing my request is System.Web.DefaultHTTPHandler, which does not implement IRequiresSessionState, and so when my request is being handled inside Global ASAX there is no session created. So, it seems like the solution will be to write a custom HTTPHandler that implements IRequiresSessionState, and use that for all my .aspx files. Even then, a session state is not created in Global.ASAX until PreRequestHandlerExecute is raised. So, putting it all together I think I need to write a custom HTTP Handler that implements IRequiresSessionState, and delay the redirection of the page until PreRequestHandlerExecute is raised where I will be able to store my flag into the Session state, and only after that, redirect my page.
Not very elegant, and I wonder if there will be any performance implications.
Have you considered using the query string for that?
Response.Redirect("/" & dv(0)("folder") & "?Raise404=true")
Then, in your Master Page, simply check QueryString("Raise404") and act accordingly.
The only drawback I can see is that a malicious client could deliberately add Raise404=true to the query string, which is not possible with your current solution. However, I do not see how that could do any harm.
As I suspected, my problem was directly tied to the fact that IIS 8 will avoid using an HTTPHandler that implements IRequiresSessionState if for performance reasons it detects that a session object is not needed, and so because the redirect code was happening during the Application_BeginRequest event, IIS was handling up to that point my requests using System.Web.DefaultHTTPHandler.
So, upon further research, I found this which was tremendous help:
IRequiresSessionState - how do I use it?
And I also documented myself on this article:
http://forums.iis.net/t/1094546.aspx
What solved my problem was to write a dummy HTTPHandler class. I added a new class to my app_code folder:
Imports System.Web
Public Class HTTPRequestHandler : Implements IHttpHandler, IRequiresSessionState
Private OriginalHandler As IHttpHandler
Public Sub New(handler As IHttpHandler)
Me.OriginalHandler = handler
End Sub
Public Sub ProcessRequest(context As HttpContext) Implements IHttpHandler.ProcessRequest
Throw New InvalidOperationException("HTTPRequestHandler cannot process requests.")
End Sub
Public ReadOnly Property IsReusable() As Boolean Implements IHttpHandler.IsReusable
Get
Return True
End Get
End Property
Public ReadOnly Property Handler() As IHttpHandler
Get
If Me.OriginalHandler IsNot Nothing Then
Return Me.OriginalHandler
End If
Return HttpContext.Current.Handler
End Get
End Property
End Class
Now, during Application_BeginRequest, session is not available but HttpContext.Current.Items is available. So, as the second article suggests, during PostMapRequestHandler I put a condition:
If HttpContext.Current.Items("Raise404") IsNot Nothing Then
Context.Handler = New MyNameSpace.HTTPRequestHandler(Context.Handler)
End If
And finally, that created the session object that I could finally use during PreRequestHandlerExecute:
HttpContext.Current.Session("Raise404") = "true"
Response.Redirect(HttpContext.Current.Items("Redirect"))
I will definitely not argue that this is not elegant, but it works and I can raise my 404 error during Page_PreRender on my master page.
I hope this may help others.
Cheers.

How to get a public variable (in a Module) to NOT share value between users

I'm working in an ASP.NET (VB) Web Application with Windows/Active Directory Authentication
I am using a module so that I can call public subroutines and functions, and reference variables, without having to instantiate a new object to access them on each page.
Within that module, I have some Public variables that I am using in multiple pages throughout the web application. I've recently realized that the values for these public variables in the module get shared between all users.
THE GOAL:
I want the value for these global variables to be specific to a single user and not shared between all sessions, and I do not want to have to instantiate a new object/class on every page that uses the variable.
THE CATCH:
I don't want to store the value in a client-side variable such as a cookie or session. I want the value to be stored on the SERVER but specific to each client/user.
The only thing I can think to do is setup a global collection/dictionary and store the variables with the authenticated user names, but then I need to have specific functions to get and set the values. While this will work, it requires all the references to these variables on all pages in the application to be updated.
EXAMPLE OF THE PROBLEM:
The below code shows how I am creating the public variable within the module and how the value is being set from one page and used on another. I'd like to continue to use this variable in the same way and share it's value between pages, but the value of the variable needs to NOT be shared between users.
-- MODULE.VB --
Public Module MyMod
Public myVariable as String = ""
End Module
-- MAINPAGE.VB --
Partial Class _Default
Sub Page_Load() Handles MyBase.Load()
myVariable = "HELLO WORLD"
End Sub
End Class
-- NEXTPAGE.VB --
Partial Class _Default
Sub Page_Load() Handles MyBase.Load()
Response.Write(myVariable)
End Sub
End Class
There are a LOT of pages in this application that will need to be manually updated if I have to use my userID-indexed collection solution, so I'm hoping there is a way to simply scope these variables differently or a way to disable the sharing between sessions.
Thanks in advance!
You didn't indicate whether or not the variables need to be persisted across page round trips or whether they are just used within each page's lifecycle.
If they are not persisted across pages, then perhaps the easiest solution is to have all of your pages inherit from a based page class and then move the values from the module into the base page. This way you won't have to change any variable references, only page inheritance.
If you do want to persist the values, completing the above changes makes it much easier to implement. You can then turn the member variables on the base page into properties and embed your user specific caching and fetching in the getter and setter.
For example, instead of:
Public MyVariable As String = ""
You would have something like:
Public Property MyVariable As String
Get
Return GlobalMyVariableCache(UserNameKey)
End Get
Set (Value As String)
GlobalMyVariableCache(UserNameKey) = Value
End Set
End Property
The problem you are coming across is a very common one in web programming. A Module's members are static - meaning there is one instance of them across the entire AppDomain of your application. Every user that accesses these will get the same object - you have already learned this.
Your options are exactly what you described. You could possibly replace the public variable in your module with a property whose getter you write to access a user-specific field in a dictionary (please remember thread safety when writing this getter code).
The much easier solution would be to use the Session. Session values are stored server-side and are user specific. The only thing that get's sent client side is the session key, and if you are using .Net authentication, this is likely already getting sent.
Good luck,

Aspx, global instance of class, possible bug in code structure

I am tracking down a bug in some old aspx code. The problem is that one some very rare occations (1/10.000 pageviews or so) two users are mixed up, ie. user A sees user B data.
Here is how the code is structured: We have a user class which is defined in a module like this:
Public Module MyGlobals
Public myUser As CMyUser
End Module
On the loginpage, we validate the username/password and if valid then the coorosponding userid is loaded from db, and we do:
FormsAuthentication.SetAuthCookie(userid, False)
Then we redirect to the secure area. In the secure areas MasterPage, on event Page_Init, we then have:
If Context.User.Identity.IsAuthenticated then
' Initialize the user class (user data is loaded)
MyGlobals.myUser = New CMyUser(Context.User.Identity.Name)
Else
' Redirect to loginpage
End If
Hereafter, is it safe to access the
MyGlobals.myUser
instance from every page which has the secure masterpage as masterpage, or could there be issues with this structure?
A VB.Net Module is like a static class with a private constructor and only static fields in C#.
That means, all variables declared in a module are shared across all threads. Hence every request(User) that's using this module will overwrite the old value.
I would strongly recommend to use Session to store user-sensitive data.
But i'm not sure why you want to store the Username because it's already stored when using FormsAuthentication(as you've shown yourself above).
If you really need this wrapper, you could easily achieve it even in a static context via HttpContext.Current.Session:
Module MyGlobals
Public Property myUser As CMyUser
Get
If HttpContext.Current.Session("CurrentUser") Is Nothing Then
Return Nothing
Else
Return DirectCast(HttpContext.Current.Session("CurrentUser"), CMyUser)
End If
End Get
Set(ByVal value As CMyUser)
HttpContext.Current.Session("CurrentUser") = value
End Set
End Property
End Module

Using IHttpModule Over Global.asax

I've been given the thrilling task of re-writing our exception handling system. Whilst I will state that handling exceptions from an application-wide point of view isn't something we want, typically it's unavoidable when our team are understaffed for the sheer amount of work we need to push out the door, so please, no flaming the globalised solution to exception handling here :)
I've had a good hunt to see what common solutions exist. At the moment we use Global.asax with the Application_Error event to do Server.GetLastError() which is placed in Session state then a redirect is called to another page where the session data is then retrieved and output in a human readable format. The redirect also calls a sproc which will carefully audit the error information which is a) e-mailed to the developers and b) viewed from a web page only viewable by developers.
The new way I've seen of doing things is using the IHttpModule interface using a class in App_Code to do something along these lines (this is my quick implementation)
Imports Microsoft.VisualBasic
Public Class ErrorModule : Implements IHttpModule
Public Sub Dispose() Implements System.Web.IHttpModule.Dispose
' Not used
End Sub
Public Sub Init(ByVal context As System.Web.HttpApplication) Implements System.Web.IHttpModule.Init
AddHandler context.Error, AddressOf context_Error
End Sub
Public Sub context_Error(ByVal sender As Object, ByVal e As EventArgs)
Dim ex As Exception = HttpContext.Current.Server.GetLastError
' do something with the error
' call the stored procedure
' redirect the user to the error page
HttpContext.Current.Server.ClearError()
HttpContext.Current.Response.Redirect("index.htm")
End Sub
End Class
My question is, what is the benefit of this solution over using Global.asax events? Additionally, what is the best way to hand the data to an error page?
EDIT: The code above does work by the way ;)
EDIT: Also, how does the HttpModule work behind the scenes? Does it just register the Error event to that particular function on application start?
UPDATE:
Upon much further investigation it seems grabbing session data is really, really messy when it comes to using IHttpModule interface. I don't think MS have matured HttpModule enough for it to be used in our particular scenario - until there are events specific to session data it's too dangerous for us to use.
Using a module has the advantage of being easily removable, all you need to do to disable it is to remove it from <httpModules> in your config.
As far as your data goes, try going with Server.Transfer or Server.RewritePath - that will keep all the current data (including the last server error).
If for some reason it clears the last error, you can save the error to HttpContext.Items before the transfer/rewrite and then retrieve it afterwards.
Edit: In response to your edit, an IHttpModule attaches to any appropriate events in it's IHttpModule.Init implementation.
HttpModule basically does the same thing as Global.asax. It's designed as a more reusable and self-contained module for event handling.

Resources