Should I be using Shared, or just a Public function and initiate a class when needed? I have read a number of articles, but still cant get my head around the best option here.
Here is the code I want to get. The userid in question is a string that's set when the user logs in. As I may need the userid in a number of pages, I want to add it in a class.
Public Shared ReadOnly Property userid As String
Get
Dim ck As HttpCookie = HttpContext.Current.Request.Cookies(FormsAuthentication.FormsCookieName)
Dim tkt As FormsAuthenticationTicket = FormsAuthentication.Decrypt(ck.Value)
Return tkt.Name
End Get
End Property
I would almost never say that you should leverage a Shared member when dealing with authentication - but since you're using Cookies I can't see any reason this code wouldn't work as is and make it easier to access.
It is perfectly fine and acceptable. You can see accessing HttpContext.Current inside static method in a lot of open source projects.
FYI: HttpContext.Current must be on current calling thread, so make sure to check HttpContext.Current is not null.
Related
For an ASP.Net application using VB how can I reference an event or textbox not located within the same file.
For example when you are coding on say Default.aspx and you put a textbox on the page this works to reference it.
Dim username As String
username = Textbox1.Text
Ok but now I want to get the values and response and process it (amongst other tasks) in a separate module say security.vb.
how can I effectively call it from security.vb so it says username = "Default.aspx".Textbox1.Text
i have tried many versions to achieve this and Google'd but I don't know the correct terms to search so am not getting a good result.
The closest Stack question is Reference from Module but that doesn't have an answer. I know this must be so simple but it eludes me.
Another "module"? You're not using classes? Using classes would make this easy:
In Default.aspx.vb
username = Textbox1.Text
Dim security As New Security(username)
In Security.vb:
Public Sub New(ByVal username as String)
Me.username = username
End Sub
Private username as String
Then you can access the username variable in your Security class whenever you need it. (Note that since the username variable is not declared as Shared, it will only be valid for the current instance of the Security class that you created in Default.aspx.vb. You could make it Shared, but that would be a bad idea on a web server, since if you did, that would mean that only one user could be logged in at a time, and whenever Bob logs in, Alice's session suddenly starts displaying Bob's data!)
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
In my webpage i use FormsAuthentication
FormsAuthentication.RedirectFromLoginPage(VisitorEmail, False)
Every time the visitor gets authenticated via the login page, i set the
Session("MemberID") = GetMemberIDByEmail(VisitorEmail) for later processing.
Since i need both MemberID and VisitorEmail.
But something tells me that this is "out of the book" and not "by the book".
So am i doing something WRONG or BAD here?
Sorry, I'm not sure exactly what you are trying to do from your description, but there's no need to store the MemberID in session state. Whenever you need it, just call:
Membership.GetUser.ProviderUserKey
Note: Its not really considered good form to store information in Session state as this could be lost e.g. if the web server resets - which it does periodically, or if the site needs to recompile. Also, its not very scalable as each "active" user will use up memory and also if you ever need to move to a web farm session state can cause issues as it will be different on each web server.
Prob OK for a little, quick site though ;-)
It's fine to use Session to cache this type of info, but remember to reassign it when the session expires in Global.asax:
void Session_Start(object sender, EventArgs e)
{
if(Request.IsAuthenticated) //to make sure the user has not logged out
Session["MemberID"] = GetMemberIDByEmail(VisitorEmail);
}
You could create a custom principal class so you can add the additional properties. Then modify your Global.asax to override Application_PostAuthenticateRequest with your code and also set Context.User = Thread.CurrentPrincipal = myPrincipal;. Best is to always set Thread.CurrentPrincipal, but normally you can also get to your own properties elsewhere in your code using the more "convenient" Page.User or Context.User.
Context.User vs. Thread.CurrentPrincipal / why FormsAuthentication can be subtle
Set custom IIdentity or IPrincipal / Store user id in Principal or Identity?
Could you not switch the two around and store the member id in the form variable (since I assume the user is able to change there email address and not there member id)...
Dim memberId as Integer = GetMemberIDByEmail(VisitorEmail)
' assuming integer here and that a result is found etc etc
' set the form authentication stuff
FormsAuthentication.RedirectFromLoginPage(memberId, False)
And then you can always look up the email address from the memberId (caching it perhaps against the member id across requests)
Public Function GetMemberEmail(Byval memberId as Integer) As String
Dim cacheKey as String = "member-email-" & memberId
Dim email as String
If Cache.Item(cacheKey) is Nothing Then
email = GetMemberEmailByID(memberId)
Cache.Insert(cacheKey, email ...
Else
email = Cache.Item(cacheKey)
End If
return email
End Function
If you need both pieces of information, and the Id is less likely to change, it would seem the better value to be used for your forms authentication....and you can always look up the email address from the value.
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.
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