Sql data reader in classes - asp.net

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).

Related

Referencing other aspx controls events in a module

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!)

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.

Shared methods in ASP.NET sessions

As a followup to the previous question I have asked "ASP.Net Architecture Specific to Shared/Static functions"
I am still struggling to understand the implications of using shared methods in ASP.NET.
So for example let us take the following code.
Public Shared Function GetCategoryByID(ByVal CategoryID As Guid) As Category
If Not CategoryID.Equals(Guid.Empty) Then
Dim res As New Category
Using sqlConn As New SqlConnection(My.Settings.ConnectionString)
sqlConn.Open()
Using dr As IDataReader = CategoryDataLayer.GetCategoryByIDQuery(sqlConn, CategoryID)
Return CType(BaseDataLayer.FillObject(res, dr), Category)
End Using
End Using
Else
Return Nothing
End If
End Function
Now I imagine client 1 connecting and running this method GetCategoryByID() from their session with the guid {A20E625F-2333-4742-BFD9-05BE7649222D}.
Let us now say that for example the process is about to execute the following line.
Using dr As IDataReader = CategoryDataLayer.GetCategoryByIDQuery(sqlConn, CategoryID)
At this point client 2 runs the same shared method but with the guid {6D806B82-FC7F-4124-AFB9-45E2689BC9F5}.
Does CategoryID not at this point become {6D806B82-FC7F-4124-AFB9-45E2689BC9F5} and therefor interfere with client 1 because now CategoryID has changed given that this is a shared method?
Could someone please clarify?
PS: I do apologize for what is essentially a duplicate post but in retrospect I don't feel the answer in the original post was clear enough (at least for me). Perhaps I wasn't specific enough either...
In your code sample, the variable res is a local variable of the shared method. No one, not even another shared method can touch that variable. It will exist for the lifetime of the method and then its gone. CategoryId is the exact same, its a local variable that cannot be touched from outside.
VB has another concept called Static which is very different than the C# version of static which can cause some confusion.
The only issue with Shared methods is shared state.
If you use a Shared field or Static variable, it will be shared across requests, and cause trouble.
However, a Shared method that doesn't use any external state or shared objects will work fine.
Even though the same method is being run, it is being run in two different contexts. Any variables local to that method (including the CategoryId parameter) are not shared.

3 tier application pattern suggestion

I have attempted to make my first 3 tier application. In the process I have run into one problem I am yet to find an optimal solution for.
Basically all my objects use an IFillable interface which forces the implementation of a sub as follows
Public Sub Fill(ByVal Datareader As Data.IDataReader) Implements IFillable.Fill
This sub then expects the Ids from the datareader will be identical to the properties of the object as such.
Me.m_StockID = Datareader.GetGuid(Datareader.GetOrdinal("StockID"))
In the end I end up with a datalayer that looks something like this.
Public Shared Function GetStockByID(ByVal ConnectionString As String, ByVal StockID As Guid) As Stock
Dim res As New Stock
Using sqlConn As New SqlConnection(ConnectionString)
sqlConn.Open()
res.Fill(StockDataLayer.GetStockByIDQuery(sqlConn, StockID))
End Using
Return res
End Function
Mostly this pattern seems to make sense. However my problem is, lets say I want to implement a property for Stock called StockBarcodeList. Under the above mentioned pattern any way I implement this property I will need to pass a connectionstring to it which obviously breaks my attempt at layer separation.
Does anyone have any suggestions on how I might be able to solve this problem or am I going about this the completely wrong way? Does anyone have any suggestions on how I might improve my implementation? Please note however I am deliberately trying to avoid using the dataset in any form.
Use the app.config file for your connection string.
Is there a particular reason you pass ConnectionString at all? It seems like a configuration value to me? So using something like a constant (or a Config singleton) might be a better idea.

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