How does a class member gets updated automatically in a Sub routine / function even if I pass in ByVal in VB.NET? - asp.net

Can anyone help explain why the following will change a value in an object's member even if I pass it in ByVal?
Scenario: The name of the person is 'Sab' but when i pass the object in the sub routine, the name changes to 'John'.
I thought object types are passed in by reference only and should not be changed unless forced. Is this a feature in .NET and what is this behavior called?
Sub Main()
Dim p As New Person
p.name = "Sab"
DoSomething(p)
Console.WriteLine(p.name) ' John
Console.Read()
End Sub
Public Sub DoSomething(ByVal p As Person)
p.name = "John"
End Sub

Writing to p.name is not the same as writing to p. ByVal prevents the parameter itself from being modified, e.g.
p = New Person
If you want to prevent the properties of Person from being written to, then you need to re-design the Person class to be immutable instead of mutable. Whether this is an appropriate thing to do depends on how you want your code to behave.
Example:
Public Class Person
' All fields are private
Private _name As String
' All properties are read only
Public ReadOnly Property Name As String
Get
Return _name
End Get
End Property
' Field values can *only* be set in the constructor
Public Sub New(name As String)
_name = name
End Sub
End Class

An instance of an object is a reference - it's a pointer to the object. If you pass any object by value, you are passing it's reference by value. Effectively, there is no difference in passing an object by value or by reference. .Net creates a new copy of the reference and passes it's value to your method but the new copy of the reference still points to the same object. Some folks say that "all objects are passed by reference" but this is not true, the reference to the object in the called method is NOT the same as the reference in the caller but they point to the same object.
If you really want to pass a copy of the object such that the called method may not modify the originals' properties, then you need to create a copy. See discussions about shallow vs deep copies and be careful to understand references to objects vs simple data types. Do think about your design though. It's rare to actually need to create a copy rather than a new object.

Related

Using Scripting Dictionary to store Objects in MS Access to avoid circular references and allow forms to know their owner objects

I’m building an Access database with classes e.g clsOrder, clsCustomer etc which manage the interface with tables. These classes create instances of forms when displaying their data. I found that once the execution of code was within one of these forms I couldn’t refer to the parent object that created it (so is there a better way of doing this? would be part of my question).
To deal with this I’m using a scripting dictionary to store instances of classes with a key using the ID of the class and a unique identifier for the class (e.g Order-3265). I then store a reference to the owner object in the form itself.
So when an object is created and its ID is known it puts a pointer to itself in the dictionary and gives that pointer to its form (hope that’s clear enough).
This then allows the form to interact with its owner class.
I’m using another class clsManager to do the adding of items to the Dictionary or retrieval or removal (with destruction).
Examples of classes - seriously cut down..
clsManager:
Public WorkingObjects As New Scripting.Dictionary
Public Function AddWorkingObject(key As String, ObjectType As Object) As Boolean
If Me.WorkingObjects.Exists(key) Then
Me.WorkingObjects.Remove key
Me.WorkingObjects.Add key, ObjectType
Else
Me.WorkingObjects.Add key, ObjectType
End If
End Function
Public Function GetWorkingObject(key As String) As Object
If Me.WorkingObjects.Exists(key) Then
Set GetWorkingObject = Me.WorkingObjects(key)
Else
Set GetWorkingObject = Nothing
End If
End Function
Public Function DestroyObject(obj As Object) As Boolean
Dim key As String
If Not obj Is Nothing Then
key = obj.DictionaryKey
If Me.WorkingObjects.Exists(key) Then
Me.WorkingObjects.Remove (key)
Set obj = Nothing
If obj Is Nothing Then
Debug.Print key & " destroyed"
Else
Debug.Print obj.DictionaryKey & " NOT destroyed"
End If
End If
Set obj = Nothing
End If
End Function
clsQuote:
Option Compare Database
Option Explicit
'use a form using an instance of this class to control manipulation of Quote records
'Loading and saving set default values if a null value is detected
Private Const scTABLE As String = "tblQuote"
Private intID As Long 'unique identifier
Private intCustomerID As Long
Private intSiteID As Long
Private rsQuoteTotalValues As DAO.Recordset
Private oCustomer As clsCustomer
Const ObjectType = "Quote-"
Private oEditForm As Form_frmQuote
Property Get EditForm() As Form_frmQuote
Set EditForm = oEditForm
End Property
Property Get ID() As Long
ID = intID
End Property
Property Let ID(QuoteID As Long)
intID = QuoteID
Me.EditForm.ID = QuoteID
End Property
Property Get Customer() As clsCustomer
Set Customer = oCustomer
End Property
Property Let CustomerID(ID As Long)
intCustomerID = ID
oCustomer.Load (ID)
EditForm.SiteID.RowSource = oCustomer.AddressSQL
EditForm.SiteID.Requery
EditForm.ContactID.RowSource = oCustomer.ContactsSQL
EditForm.ContactID.Requery
EditForm.CustomerID = ID
End Property
Property Get DictionaryKey() As String
DictionaryKey = ObjectType & CStr(Me.ID)
End Property
'END PROPERTIES//////////////////////////////////
Public Sub DisplayForm(Visibility As Boolean)
With Me.EditForm
.Visible = False
.subFrmQuoteSectionsSummary.SourceObject = ""
If Visibility = True Then
...some stuff...
.Visible = True
End If
End With
End Sub
Public Function Load(ID As Long) As Boolean
'On Error GoTo HandleError
Dim RS As DAO.Recordset
Dim sQry As String
Load = False
If Nz(ID, 0) <> 0 Then
sQry = "SELECT * FROM " & scTABLE & " WHERE ([ID]=" & ID & ");"
Set RS = Manager.DB().OpenRecordset(sQry, dbOpenForwardOnly)
With RS
If .RecordCount = 0 Then
MsgBox "Cannot find Quote with ID = " & ID, vbCritical
GoTo Done
End If
Me.ID = Nz(!ID, 0)
Me.CustomerID = Nz(!CustomerID, 0)
Manager.AddWorkingObject Me.DictionaryKey, Me
Me.EditForm.SetOwnerObject (Me.DictionaryKey)
.Close
End With
Set RS = Nothing
Load = True
End If
Done:
Exit Function
HandleError:
MsgBox "Error in Customer Load: " & vbCrLf & Err.Description, vbCritical
Resume Done
End Function
Private Sub Class_Initialize()
Debug.Print "Quote class initialized"
Set oCustomer = New clsCustomer
Set oEditForm = New Form_frmQuote
Me.ID = 0
Set oQuoteTidier = New clsClassTidier
Me.DisplayForm (False)
End Sub
Private Sub Class_Terminate()
Set oCustomer = Nothing
Set oEditForm = Nothing
Debug.Print "Quote class terminated"
End Sub
From the EditForm:
Option Compare Database
Option Explicit
'necessary for the object to have a reference to its owner in this manner to prevent circular reference
Private OwnerObject As clsQuote
Public Function SetOwnerObject(OwnerKey As String) As Boolean
SetOwnerObject = False
Set OwnerObject = Manager.GetWorkingObject(OwnerKey)
SetOwnerObject = True
End Function
Private Sub cmdClose_Click()
OwnerObject.EditForm.Visible = False
Manager.DestroyObject OwnerObject
DoCmd.Close acForm, Me.Name
End Sub
Each business object class (like ClsOrder) has an editForm instance which is loaded and hidden until required and a up to 3 DAO Recordsets that it keeps open.
I think all references to the business objects that are interrelated are pointers to the objects in the dictionary.
My problem is error 3035 exceeding system resources. I’ve checked objects are destroyed when not in use but repeatedly opening and closing objects gets me to error 3035.
So the question is- am I just asking Access to do stuff it can’t or would better programming fix it?
I see ZERO reasons to write all that code. Why not let a form handle all of this? Remember, each form is in fact a "class" instance. You can even launch multiple copies of a single form, each with their own code, own data and each instance of the SAME form can operate 100% independent of other working copies of that same form.
If you attempting to look at this problem and wanting to have a class object for a form, then just use the form object - that's what it does for you!
I see zero benefits from writing all that code. While .net has the dataset manager and system (and now the very similar entity framework, this is MUCH done since .net does not have data bound forms.
In Access, each form is in fact a class object. And that includes any public sub or function for that form (so functions become methods of that form, and public vars become properties of that form). In addition to the bound form having a truckload events, these events work as actions against any data editing. So, unlike most systems, you have "on change" event, before update event, after update event. So, by simply adoptiing a bound form, then you get:
A class object is automatic created for you.
You can have multiple instances of that class, and hence multiple instances of that same form open at the same time.
You get all of those data events that can be used for verifiction of data input (or have the user not save the record until such time your critera is met.
You have full use of all data columns, even if controls are NOT placed on the form bound to those columns. So, you even get intel-sense for all of the data columns - that is you map.
I am not aware that there is some big huge circular reference problem here. This is like stubbing your toe, but then going to the doctor for some huge open heart by-pass operation. So to go on some huge massive coding spree, and chew up huge amounts of developer dollars for some "rare" issue of some kind of rare and un-seen circular reference issue is essentially a huge wild goose chase that will only have you chewing up huge amounts of developer code and time when NONE is required at all.
I mean, if you have say 3 instances of the SAME form open? Then how does the code know and refernce what insance of that form? Well, the EXACT same approac used in typical OO programming can and should be used here. That approach means you don't HARD CODE the forms! name or referances in code EVER. You never want to do this.
So, if you are in a sub form, and need to referacne say data or controls in the parent form?
You could do this:
strLastName = forms!frmCustomer!LastName
In above, we have hard coded the forms name. You don't want to do that.
In that subform, the correct way to write this code is:
strLastName = me.Parent.form!LastName
Now, note how the above referances the parent form. So, that code will work EVEN if we have 3 copies of the frmCustomer active at the same time. You can full refernce ANYTHING in a form by its object "instance". So, in JavaScrip, or c#, you often see "this.SomProperty" as a refeance to that object.
In access, you can do the same thing, and use "me". Or me.Parent.From to reference the parent form. So, as a general approach here, you should NEVER have to hard code forms reference. If you take this approach, then all issues of circular referencing will not only be eliminated, but then you are using a classic and traditional approach to object programming, and object referencing. While Access is not full OO, it certainly follows a lot of OO design concepts, and how forms work in Access are most certainly instances of a object.
Attempting to write truckloads of code when the forms object model already exists as a "single" class object instance of that form makes no sense, and is not required, and the road you going down will likely hamper and reduce your abilities to deal with the fantastic instance of that form you already have.
As noted, the form already has the dictionaly and columns attached, and Access EVEN generates the members for your automatic. The result is you can reference any column of the table that the form is bound to with
me.LastName
me!LastName
While the above two formats are allowed, the first (me + dot + column name) is in fact a member of the forms class. You will find that if you use code to set the forms data source, then often these members are NOT generated for you, and thus you have to use the ! (bang) to reference columns from the table for that form.
So, I don't grasp while you attempting all that extra code when the form has all of the abilities you are asking for in a class object.

Newbie to using Classes Properly - How to Set a Property or Class Value to return of a Function

I have been building various web based programs and things for a while, but am quite new to .NET and doing things "properly." As I am completely self taught, with help from sites like this and so on, my understanding of fundamentals is limited.
So, I have a series of functions which return data that I want depending on parameters put in, this is very basic stuff, and obviously all works. However, I am trying to make it easier to call these functions by using Classes.
So, say I have a function which returns a populated DropDownList converted to an HTML string
Function GetList(ListRequired as String) as String
' Do stuff to return a dropdownlist whos contend is determined by `ListRequired`, converted to an HTML string
End Function
In this example, it works fine, but to use it I must know what to enter for "ListRequired" to get what I want.
So, let's say, the options for the ListRequired para are "mastercategory", "brandlist", "priceranges" to return a different set of lists - each option would send the code off the retrieve information from a database and return accordingly.
Suppose I want a third party developer to be able to call this function with the most basic amount of "instruction" required, and not even have to tell him the list of available ListRequired by making it available as a Class.
Public Class DropDownLists
Public Property MasterCategory
Public Property BrandList
Sub New()
Me.MasterCategory = HTMLControls.RenderSearchFilters("mastercategory")
Me.BrandList = HTMLControls.RenderSearchFilters("brandList")
End Sub
End Class
A developer can then call this from Visual Studio/VWD etc very simply:
Dim dd As New DropDownLists
Dim list1Html as String = dd.MasterCategory
Dim list2Html as String = dd.BrandList
Because VWD etc creates all the handy helpers and displays which properties the Class exposes, it is very easy to use this code without have to constantly refer to a manual.
However... when creating a new instance of the Class:
Dim dd As New DropDownLists
This will cause the server to process all the functions within the class which create the Properties, which would be desperately inefficient if there are lots of properties.
So I have tried using my own interpretation of the logic and written this:
Public Class DropDownLists
Shared Property Master
Shared Property Brand
Sub New()
End Sub
Public Class MasterCategory
Sub New()
DropDownLists.Master = HTMLControls.RenderSearchFilters("mastercategory")
End Sub
End Class
Public Class BrandList
Sub New()
DropDownLists.Brand = HTMLControls.RenderSearchFilters("brandList")
End Sub
End Class
End Class
Hoping I'd be able to create the HTML for a Master Category drop down like:
Dim dd as New DropDownLists.MasterCategory
But that doesn't work, and upon reflection I think I can see why... it's not returning the string, but creating a new type.
So... my question is...
What is the correct way to achieve what I am looking for, which is, to be able to create these string outputs by typing
Dim dd As New DropDownLists
Dim list1Html as String = dd.MasterCategory
Dim list2Html as String = dd.BrandList
Without having to pass potentially unknown string parameters, or causing ALL properties to be created each time the DropDownLists Class is created, ie, only run the code for the output I need.
I'm expanding my comment to give you a clearer idea of what I meant:
Public Class DropDownLists
Enum ListType
Undefined
MasterCategory
Brandlist
End Enum
Public Shared Function GetList(ListRequired As ListType) As String
Select Case ListRequired
Case ListType.Brandlist
Return . . .
Case ListType.MasterCategory
Return . . .
Case ListType.Undefined
Throw New . . . .
End Select
End Function
End Class

Structuring Class with constructor

I have a Class with a Constructor. My constructor takses all the required parameter, then I use the parameters to return another class.
Public Class SearchResults()
private id as int
Sub New(id) ' Constructor
getResults(id)
End Sub
Public Function getResults(byval id as int) as List(of Products)
........
return list (of products)
End Function
End
Then in my page I want to get all the productts.
Dim s = new SearchResults( 1 )
Dim p as list(of Products) = s.getResults()
----now I can do for Each on this
This works for me, but I do have a feeling there has to be a better way of doing it, I want to stick to the best standards. do you guys think, this is the best approach or it could be improved? My designers will be utilizing this class within their projects...so i would love to make it as simple as possible for them.
or maybe how to create an Collection of a Class...for example Class Product and its collection Class Products, Where Products is the Collection of Project Class.
There are three ways to do what you want:
1) You can load the results and store them in the object on the constructor
2) You can store the ID in the object and call the function to get the results when needed
3) You can make the function static and pass in the ID as well as the collection (or resource needed to get the collection) and not use an object at all.
Depending on what you need, most likely either 1 or 3 are going to be best. If you plan on reusing the object for other methods than just the one function, then option 1 is the best, but if you just need the 1 function, go with option 3 like this:
Public Class SearchResults
Public Static Function getResults(ByVal id As int) As List(Of Products)
'........
return list (of products)
End Function
End Class
You could make the getResults method static possibly and cut out having to create a class instance to call it.
I personally would declare the getResults method as Shared so that you don't have to create a SearchResults object just to access that method. This would also allow you to call the method again with another ID without having to create another object.

Not able to access class level private variables from different methods/functions of the same class

In the following I am trying to define a Private variable on Class level called _p. The HTTP.POST for Index will bring a User provided value which I'll set this private variable with. In the second Method called ListOfVehicles, I'll be accessing this variable.
Now everything is alright theoretically, however when I try to access this private variable I don't get anything, this is found Nothing.
Public Class QuotationController
Inherits System.Web.Mvc.Controller
'Private Variables
Dim _p As String
'Get Basic pickup and dropoff details
Function Index() As ActionResult
Return View()
End Function
'Function to get basic details out of the view
'and to redirect to ListOfVehicles
<HttpPost()>
Function Index(ByVal P As String, ByVal D As String) As ActionResult
_p = P
Return RedirectToAction("ListOfVehicles")
End Function
'Show list of vehicels
Function ListofVehicles() As ActionResult
ViewData("UserChoice") = "Pickup: " & _p
vehicleList = QB.GetQuotation(_p, _d)
Return View(vehicleList)
End Function
End Class
That is fundamentally impossible.
Each HTTP request gets a separate controller instance; they don't share anything.
You should use cookies, session, application state, or cache, as appropriate.
In your case, you should probably include that variable in a POST to the other action from a <form>.
If you don't want to add a formal post parameter you can use
TempData.Add("P", P);
just before the return statement, in your ListOfVeicles you can accesso via
string p = TempData["P"];
Temp data is valid just within the request scope
EDIT: sorry for C# syntax, I'm not using VB since the good old days ov VB 6

Extend System.Web.HttpContext.User

I would like to extend the System.Web.HttpContext.User object (ASP.NET/VB.NET) so that it contains other fields besides just Name. I understand I can create an object that inherits the System.Security.Principal.GenericPrincipal class, but how do I store that in the Current.User object in a usable fashion. ie, I can do something like Current.User.UserID.
So far to achieve this I've created a kludgy workaround by using | delimited strings in the User.Name property and then splitting them, but it's getting kind of ridiculous.
Any suggestions?
Thanks!
EDIT: I have tried the following to no avail:
Imports System.Security.Principal
Public Class CurrentUser : Inherits GenericPrincipal
Private _totalpoints As Integer
Private _sentencecount As Integer
Private _probationuntil As DateTime
Public ReadOnly Property TotalPoints() As Integer
Get
Return _totalpoints
End Get
End Property
Public ReadOnly Property SentenceCount() As Integer
Get
Return _sentencecount
End Get
End Property
Public ReadOnly Property ProbationUntil() As DateTime
Get
Return _probationuntil
End Get
End Property
Public Sub New(ByVal principle As IIdentity, ByVal roles() As String, _
ByVal points As Integer, ByVal sentences As Integer, ByVal probationTil As DateTime)
MyBase.New(principle, roles)
_totalpoints = points
_sentencecount = sentences
_probationuntil = FixDBNull(probationTil)
End Sub
End Class
setting the object in my Global.asax Application_AuthenticateRequest function like so:
HttpContext.Current.User = New CurrentUser(User, userRoles, _
points, sentenceCount, probationUntil)
with a direct cast wherever the object is needed like so:
Dim thisUser As CurrentUser = DirectCast(Current.User, CurrentUser)
i also tried CType and it didn't work... my error is
[InvalidCastException: Unable to cast object of type 'System.Security.Principal.GenericPrincipal' to type 'myProject.CurrentUser'.]
i'm losing my mind here ... :( thanks guys...
anyone?
You can create your own Principal class with the required properties, that inherits from a Generic Principal, and then set the User property of your Current Context to be the a user of that type.
The example below is for ASP.Net MVC but a similar approach could be used with webforms.
You can do this in the PostAuthenticateRequest after a user is authenticated (in the Global.asax)
private void MvcApplication_PostAuthenticateRequest(object sender, EventArgs e)
{
SomePrincipal newUser = new SomePrincipal(User.Identity, tmpRoles);
senderRef.Context.User = newUser;
System.Threading.Thread.CurrentPrincipal = newUser;
}
You could then add a property or method in a base class of your page (or controller) for example that to wrap and type the Context.User principal to your Principal type and make sure you call it rather than calling the one on the HttpContext.
There are probably other solutions too!
Would this approach work for you? It looks a little involved but it really doesn't take too long to setup:
Create a 'base' class of your own, and have your pages inherit from that. For example, create a base class called 'BasePage' which inherits from System.Web.UI.Page.
Have your ASP.net pages inherit from your new BasePage class.
In the BasePage class, you can have a public property which contains the extra fields you want to store for your user (eg. BasePage.FirstName, BasePage.LastName). Better still, create a User object containing the extra fields, and expose that via BasePage, eg. "BasePage.Customer". This keeps things tidy if you plan to extend BasePage later.
You can then override the OnInit() of the base class to check for HTTPContext.Current.User.Name property, and fetch the necessary info from your DB to initialise your custom properties.
You can modify the code so that it won't need to hit the database each time the page is refreshed by using ControlState to check whether the custom fields have values before populating them again from the database.
Hope this helps...
Richard.

Resources