3 tier application pattern suggestion - asp.net

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.

Related

Is it bad to instantiate request and response objects in other functions?

I could not find a similar question, and this could be a dumb question, not sure, I couldn't figure out what keywords to search for.
For example, we have some sort of request/response pair for accessing information from the database (forgive me, using VB .NET at work, not my choice, so I'm just staying consistent)
Public Class ItemAddRequest
Public param1 As String = ""
Public param2 As String = ""
End Class
Public Class ItemAddResponse
Public returnParameter As MyItemObject = ""
Public Function Invoke(req As ItemAddRequst)
' SQL Queries go here
' Build my returnParameter
End Function
End Class
So these are used for the front end to get information to display on the front end, but is it bad to use these somewhere else in your code for the sole purpose of getting that info or adding that info? Generally you would want to modularize (invented word) that and use methods of my MyItemObject to do this, but we already have a large collection of things that would need to be changed so we are not doing that, at least for now. So for example we are doing something like this
Public Class ParentItemAddRequest
Public param1 As String = ""
Public param2 As String = ""
End Class
Public Class ParentItemAddResponse
Public returnParameter As MyParentItemObject = ""
Public Function Invoke(req As ParentItemAddRequest)
' SQL Query goes here to add parent
' Now also need to add a regular MyItemObject
Dim itemReq as new ItemAddRequest()
Dim itemResp as new ItemAddResponse()
itemReq.param1 = 'whatever
itemReq.param2 = 'whatever
itemResp.Invoke(itemReq)
me.returnParameter = itemResp.returnParameter
End Function
End Class
Doing this seems to work fine, but what kind of problems could we anticpate to cause? Or is this a completely normal thing? Seems odd to us. Thanks for the help.
If this code works than not much is wrong. If it aint broke then dont fix it. That being said, the only thing wrong with this code, i think, is that it uses wrong patterns. It just looks wrong. The only problem it would create is that it would confuse the hell out of new hires. Another serious implication of working this way is that the Class is now responsible for two things (1) declaring the data contract (2) defining the algo to fill it. This mixup is frowned upon according to SOLID principles. The mixup of responsibilities make it difficult to do unit testing and impact analysis.
When I find a Request class and Response class, the immediate assumption is that you guys are using the DTO pattern. The classes are assumed to be data contracts because of their naming convention. Now, the dtos are supposed to be simple POCOs devoid of any business logic. This is so that you can put all such classes in a seperate dll and different clients can use the shared data structures. So I wont be expecting the Invoke method there. I would expect that the dto is filled at the DAL layer either by handcrafted sqls in a DAO class or via some orm like entity framework.
With handcrafted sqls, I would expect a set of classes like Class ParentItemDAO with methods like Function Add(req As AddParentItemRequest) As AddParentItemResponse. Similarly I would expect a method Function GetParentItemById returning either a business object or a dto.

Populating Object with Data VB.net

I'm looking to populate an object then display the data to labels.
I've created a Student Class:
Public Class student
Public Dim sNum As Integer
Public sName As String
Public Sub New(ByVal sNum As Integer)
MyBase.New()
Me.sNum = sNum
End Sub
I've got a database class that I want to use to populate this.
Public Function populateStudent() As Object
Dim ObjStudent As New student(1)
ObjStudent.sName = "Bitz"
Return ObjStudent
End Function
Obviously this is just a step, eventually I'll be querying the database to populate the data, but I want to get this working first so I know I'm creating this correctly.
In my main class attached to my .aspx I want to be able to do
lblStudentName.Text = ObjStudent.sName
Am I going about this correctly, or is there a better way?
You need not have
MyBase.New()
because you don't have a explicit base class.
The return type of populateStudent() of Object does not make much sense; it should be either a list of Student if you are planning to return a collection of student after querying the db. if you are planning on populating the view from this method itself, then it should be a Sub returning nothing and not a Function.
Otherwise everything else looks okay.
EDIT:
Sounds like you need something like this.
Public Function populateStudent(Id as String) As student
Dim ObjStudent As New student(1)
ObjStudent.sName = "Bitz"
Return ObjStudent
End Function
Close. You'll want to set the .Text property on the Label control:
lblStudentName.Text = ObjStudent.sName
(which you have since edited your question to contain... it often bothers me that SO doesn't show that something was edited if the edit is very soon after the initial post)
As for a "better way" just remember that there are many, many ways to do just about anything. "Better" is very relative and depends on other factors not present in the code you have so far. As of now, you have a method which returns an instance of an object (similar to the Factory pattern, feel free to research more on that and other patterns) and you use properties on that object to populate data fields in the UI. Pretty straightforward, nothing wrong with it.
As the system grows and the problem domain becomes more complex, there will be more definition of "good design" vs. "bad design." But in just getting started, this is perfectly fine.

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.

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

ASP.NET: Object reference not set to an instance of an object

I have a VB.NET site. At the top of many of my pages I have code such as this:
Partial Class _Default
Inherits System.Web.UI.Page
Dim fns As New Functions
Dim bOnOff As Boolean
Dim LNBs As New LimsNetBusiness.SiteUI.SiteUI
Dim LNBu As New LimsNetBusiness.User.user
Dim LNBp As New LimsNetBusiness.PasswordFunctions.Password
When I publish the site. I get "Object reference not set to an instance of an object." on line:
Dim LNBs As New LimsNetBusiness.SiteUI.SiteUI
Why? And how do I fix?
You would need to show us the constructor of LimsNetBusiness.SiteUI.SiteUI I would assume.
Given that this problem only happens remotely, I'm thinking the constructor accesses an asset, connection or config file that isn't available on the server.
My recommendation is to open the DLL with Reflector and see what resources it accesses/other potentials for a null dereference.
Oddly you are saying there is no Sub New(), but I'm curious how you can create a variable of that type without having a constructor.
You mention that SiteUI passes through to your data layer - are you confident the data layer access is working fine remotely?
Not enough info. Unfortunately LimsNetBusiness is not a .net namespace. I would suggest looking into the SiteUI constructor and see if you fail inside there.
Is LimsNetBusiness a seperate DLL? Did you publish that too?
Does LimsNetBusiness.SiteUI.SiteUI reference a table, or perhaps web.config file? Maybe a table row was deleted or something similar.
This is a NullReferenceException Some where along the way a NullReferenceException is occurring.
Now you didn't provide enough information about what LimsNetBusiness is but if I had to guess:
Since I can't see you stack trace, you should be aware of the fact that the exception might be contained in the code that is instantiated in the constructor of LimsNetBusiness.SiteUI.SiteUI
If LimsNetBusiness.SiteUI is a static Property, you will need to make sure that you instantiate the returned object.
It's possible that the error is occuring in the initialization of LNBs. Since that happens in the dim statement, you probably won't see the location of the error if this is true. You can try moving the initialization of LNBs to an assignment statement:
Dim LNBs As LimsNetBusiness.SiteUI.SiteUI
LNBs = new LimsNetBusiness.SiteUI.SiteUI
Also, check in the initialization of LimsNetBusiness.SiteUI.SiteUI and make sure there is a "new" everywhere that there should be.

Resources