"Best" way to access IO.FileInfo Protected property (OriginalPath)? - vb.net

I'm working on a series of methods that will test a given path to determine if it is (or, at least could be) valid. I have a couple of overloads that will accept either an IO.FileInfo object or an IO.DirectoryInfo object - which I'd personally prefer to use to help in catching invalid characters as soon as possible in the process. One piece of information I "need" to get as a part of the process is basically the original value the user presented - especially in the case of an IO.FileInfo or IO.DirectoryInfo object as those will automatically prepend the Environment.CurrentDirectory value to any string it determines to be a relative path.
For example:
Dim TestFile As New IO.FileInfo("Testing\This is a test")
Instantiates an IO.FileInfo object with the following properties (among others):
DirectoryName - "C:\Development\My Solution\My Project\bin\Debug\Testing"
Exists - False
Extension - ""
FullName - "C:\Development\My Solution\My Project\bin\Debug\Testing\This is a test"
Name - "This is a test"
OriginalPath - "Testing\This is a test"
This last property is the one I'm most interested in. It's a Protected property of the IO.FileInfo class, which is inherited from the IO.FileSystemInfo class, but it seems to be the best possible way to retrieve the original value passed to the IO.FileInfo constructor. Obviously, I can't access the property directly with a simple Dim TestPath As String = TestFile.OriginalPath - the IDE gives shows the error "'System.IO.FileSystemInfo.OriginalPath' is not accessible in this context because it is 'Protected'" - so I can only think of one way to get this specific piece of information: Create a whole new class that inherits IO.FileSystemInfo with a Public property for the OriginalPath that reads from the base class.
PLEASE NOTE: I do have an overloading method that accepts a String value, but I was actually thinking of trying to "hide" that one behind a Private modifier so that only FileInfo and DirectoryInfo objects could be used to call this method. My primary reasoning there is that, if a FileInfo or DirectoryInfo object fails to instantiate due to invalid characters in the string (or any other reason), there's no reason for me to make the call to this method and I may be able to "clean up" the code a little.
Regardless, the creation of a whole separate class that's really only going to be used to access this one property in this one specific scenario seems like an awful lot of overhead for what should (or, at least could) be a fairly simple thing. Unfortunately, I can't think of any other way to extract that information and I'm just wondering if I may be over-thinking this.
In the meantime, I've gone ahead and created a TempFileInfo class for testing (NOTE: I haven't actually tested it yet) without explicitly implementing any "custom" code for any of the regular Public properties (required when inheriting from the IO.FileSystemInfo class):
Private Class TempFileInfo
Inherits IO.FileSystemInfo
Public Overrides ReadOnly Property Name As String
Get
Throw New NotImplementedException()
End Get
End Property
Public Overrides ReadOnly Property Exists As Boolean
Get
Throw New NotImplementedException()
End Get
End Property
Public Overrides Sub Delete()
Throw New NotImplementedException()
End Sub
Public Shadows ReadOnly Property OriginalPath As String
Get
Return MyBase.OriginalPath
End Get
End Property
Public Shared Widening Operator CType(v As FileInfo) As TempFileInfo
Dim FSI As FileSystemInfo = v
Return CType(FSI, TempFileInfo)
End Operator
End Class
You'll note that I also included a Widening Operator that should allow me to convert from a "regular" IO.FileInfo object to my TempFileInfo object so that I can access my "custom" OriginalPath property. This is the only way I've found so far to "get there from here", but I wanted to go ahead and ask here in case there are other suggestions or things I might be overlooking.
The only "saving grace" for this method of implementation is that, because the "custom" TempFileInfo, the standard IO.FileInfo, and the standard IO.DirectoryInfo classes all inherit from the IO.FileSystemInfo class, I should be able to simply create an overloaded Widening Operator method for converting both IO.FileInfo objects as well as IO.DirectoryInfo objects instead of having to create a separate TempDirInfo class.

The path entered when a FileInfo or DirectoryInfo object is created is stored in the protected String OriginalPath field - which is used for serialization purposes - and the internal DisplayPath property, which is used as an internal reference.
When the class object is initialized (see the Init() method in the .Net source code), the path entered is stored in both the OriginalPath Field and the DisplayPath Property.
Both classes don't provide a public property to retrieve the initial path used to create the object.
They both, instead, return the DisplayPath property value as an override to the ToString() method:
Dim fInfo = New FileInfo("\SomePath\SomeFileName.ext")
Dim originalPath = fInfo.ToString()
var fInfo = new FileInfo(#"\SomePath\SomeFileName.ext");
string originalPath = fInfo.ToString();

Related

VBA Class with Collection of itself

I'm trying to create a class with a Collection in it that will hold other CASN (kind of like a linked list), I'm not sure if my instantiation of the class is correct. But every time I try to run my code below, I get the error
Object variable or With block not set
CODE BEING RUN:
If (Numbers.count > 0) Then
Dim num As CASN
For Each num In Numbers
If (num.DuplicateOf.count > 0) Then 'ERROR HERE
Debug.Print "Added " & num.REF_PO & " to list"
ListBox1.AddItem num.REF_PO
End If
Next num
End If
CLASS - CASN:
Private pWeek As String
Private pVendorName As String
Private pVendorID As String
Private pError_NUM As String
Private pREF_PO As Variant
Private pASN_INV_NUM As Variant
Private pDOC_TYPE As String
Private pERROR_TEXT As String
Private pAddressxl As Range
Private pDuplicateOf As Collection
'''''''''''''''' Instantiation of String, Long, Range etc.
'''''''''''''''' Which I know is working fine
''''''''''''''''''''''
' DuplicateOf Property
''''''''''''''''''''''
Public Property Get DuplicateOf() As Collection
Set DuplicateOf = pDuplicateOf
End Property
Public Property Let DuplicateOf(value As Collection)
Set pDuplicateOf = value
End Property
''''' What I believe may be the cause
Basically what I've done is created two Collections of class CASN and I'm trying to compare the two and see if there are any matching values related to the variable .REF_PO and if there is a match I want to add it to the cthisWeek's collection of class CASN in the DuplicateOf collection of that class.
Hopefully this make sense... I know all my code is working great up to this point of comparing the two CASN Collection's. I've thoroughly tested everything and tried a few different approaches and can't seem to find the solution
EDIT:
I found the error to my first issue but now a new issue has appeared...
This would be a relatively simple fix to your Get method:
Public Property Get DuplicateOf() As Collection
If pDuplicateOf Is Nothing Then Set pDuplicateOf = New Collection
Set DuplicateOf = pDuplicateOf
End Property
EDIT: To address your question - "So when creating a class, do I want to initialize all values to either Nothing or Null? Should I have a Class_Terminate as well?"
The answer would be "it depends" - typically there's no need to set all your class properties to some specific value: most of the non-object ones will already have the default value for their specific variable type. You just have to be aware of the impact of having unset variables - mostly when these are object-types.
Whether you need a Class_Terminate would depend on whether your class instances need to perform any "cleanup" (eg. close any open file handles or DB connections) before they get destroyed.

Dictionary comes back from cache as Null/Nothing

I'm writing some vb.net code to retrieve some data from middleware, do some stuff with it, and then save a dictionary of name/value pairs to cache for quick retrieval when needed.
It all seems to go well until I retrieve the object from cache and it is always Null/Nothing.
Yet in the watch window I can see my cached object and it has data in it.
Thinking maybe it's a serialization issue I made a new class that inherits Dictionary and is marked as serializable but I still get the same results.
So right now I have this class:
<Serializable()> Public Class SerialDict
Inherits Dictionary(Of String, String)
Public Sub New()
End Sub
End Class
I populate it and put it into cache like this:
Dim Licenses As New SerialDict
For Each r As DataRow In dtLicenses.Rows
Dim prikey As String = r("SettingID").ToString.Trim
Dim decryptionKey As String = GetHash((xx))
Dim licData As String = DecryptData(r("SettingVal"), decryptionKey)
Licenses.Add(r("SettingKey"), licData)
Next
If IsNothing(HttpContext.Current.Cache("Licenses")) Then
HttpContext.Current.Cache.Add("Licences", Licenses, Nothing, Cache.NoAbsoluteExpiration, Cache.NoSlidingExpiration, CacheItemPriority.Default, Nothing)
End If
Then elsewhere we need to check that data so I try to retrieve it like this:
Dim Licences As SerialDict = CType(HttpContext.Current.Cache("Licenses"), SerialDict)
At this point Licenses is always Nothing, but the watch window shows data in HttpContext.Current.Cache("Licenses").
Any suggestions? Thanks!

What is the difference between My.Application.Info.DirectoryPath and Application.StartupPath

The question says it all really. What is the difference? I am wanting to get the path the application has been installed to and so far have seen no discrepancy between the two.
Only difference I see in the MSDN pages is that Application.StartupPath mentions ClickOnce applications (I am not running a ClickOnce application - can't stand them!)
There are probably other ways to get it too, looking through Intellisense. Is it just a case of "more than one way to skin a cat" or are there merits and drawbacks to each method?
The types contained within the My namespace are contained within Microsoft.VisualBasic.dll - they aren't commonly (or ever!) used across other .NET languages. Those within the Application namespace are.
Under the hood, Application.StartupPath does this:
Public ReadOnly Shared Property StartupPath As String
Get
If (Application.startupPath Is Nothing) Then
Dim stringBuilder As System.Text.StringBuilder = New System.Text.StringBuilder(260)
UnsafeNativeMethods.GetModuleFileName(NativeMethods.NullHandleRef, stringBuilder, stringBuilder.Capacity)
Application.startupPath = Path.GetDirectoryName(stringBuilder.ToString())
End If
(New FileIOPermission(FileIOPermissionAccess.PathDiscovery, Application.startupPath)).Demand()
Return Application.startupPath
End Get
End Property
Whilst My.Application.Info.DirectoryPath does this:
Public ReadOnly Property DirectoryPath As String
Get
Return Path.GetDirectoryName(Me.m_Assembly.Location)
End Get
End Property
which calls this:
Public Overrides ReadOnly Property Location As String
<SecuritySafeCritical>
Get
Dim str As String = Nothing
RuntimeAssembly.GetLocation(Me.GetNativeHandle(), JitHelpers.GetStringHandleOnStack(str))
If (str IsNot Nothing) Then
(New FileIOPermission(FileIOPermissionAccess.PathDiscovery, str)).Demand()
End If
Return str
End Get
End Property
GetModuleFileName used in StartupPath is a call to the native Win32 API, and GetLocation used in DirectoryPath involves a "native" call to the .NET CLR Runtime, so you'd need to dig even deeper to find out where it gets its information from.
TL;DR
Use Application.StartupPath as a preference and to help develop good habits, since it doesn't rely on the Microsoft.VisualBasic additions to .NET, and will make the transition to other languages easier should you ever choose to use them.

Serializing and Searching Object Collections

I would appreciate if somebody could answer some questions regarding storing and searching object collections please. If you could give a basic example of any suggestions I would be very grateful.
Storing:
I'm writing an application which needs to search Active Directory for computer objects, and I'm storing them in a collection of objects. I want to save the computer objects collection along with additional information which is not stored in Active Directory (e.g. the computer's MAC address) for the next time the application is run. I also want to save a list of OU's.
Here is my object collection so far (It will have more properties). What is the best method of saving the collection? Preferably not using a database or will saving to a file have a drastic performance impact?
Or is there a better way to do what I have below?
Public Class Computer
Public Property Name As String
Public Property FQDN As String
Public Property Path As String
End Class
Public Class ComputerCollection
Inherits Collections.CollectionBase
Public Sub Add(ByVal computer As Computer) 'Adds a new computer object to the collection
List.Add(computer)
End Sub
End Class
Searching:
I have a layout similar to ADUC with a tree view of OU's and a list view displaying computer objects in the selected OU. The following code loops through the computer object collection checking if the path of the computer object matches the selected OU path and then displays them in the list view.
Is this the best method in terms of performance? or is there a faster way?
Private Sub tvOU_AfterSelect(sender As Object, e As TreeViewEventArgs) Handles tvOU.AfterSelect
lvComputers.Items.Clear()
If tvOU.SelectedNode.Name > "" Then
For Each computerObj In computers
If computerObj.Path.ToString.Replace("CN=" + computerObj.Name + ",", "") = tvOU.SelectedNode.Name Then
Dim lvItem As New ListViewItem
lvItem.Text = computerObj.Name
lvComputers.Items.Add(lvItem)
End If
Next
Else
Exit Sub
End If
End Sub
A simple List(Of Computer) is indeed all you need unless there is some other unknown requirement. Saving is very simple using serialization for this type of thing, and would work the same for a List or Collection<T>.
Imports System.Collections.ObjectModel
Public Class ComputersCollection
Inherits Collection(Of Computer)
' NOT the crude thing in the VB NameSpace
' there is almost nothing to add: item, add and remove
' are in the base class
' ... perhaps saving and retrieving them by a key (name)
' which may do away with the search procedure in the posted code
End Class
Private Computers As New Collection(Of Computer)
' or
Private Computers As New List(Of Computer)
The other part, saving your collection, can be simple with serialization. First, you will have to add the Serializable attribute to your Computer class:
<Serializable>
Public Class Computer
If you forget, you get the error Class foobar is not marked as serializable. Save to disk:
Imports System.Runtime.Serialization
Private myComputerFile As String = ...
Dim bf As New BinaryFormatter
Using fs As New FileStream(myComputerFile , FileMode.OpenOrCreate)
bf.Serialize(fs, Computers)
End Using
Then you can recreate the List or Collection just as easily; this time the List:
' ToDo: add a check to skip if file is not there
' as in the first time run
Dim bf As New BinaryFormatter
Using fs As New FileStream(myComputerFile , FileMode.Open)
Computers = CType(bf.Deserialize(fs), List(Of Computers))
End Using
Alternatively you can use the XML serializer to create an XML File or use the faster, smarter binary serializer is ProtoBuf-Net

How to create global object in VB NET

I'm triying to build a language dictionary in VB NET in order to be able to have several languages in the application.
This dictionary has a init method that loads from database the entire dictionary and stores it in memory.
In the rest of the classes of the project, I added the reference and I can use directly the object without create a new instance because their methods are shared.
My question is how i have to load the dictionary content in order that the rest of classes only accessing to Language.GetWord method obtains the properly record of the collection, same way as My.Settings class.
When My.Settings is used from any class the values are loaded. I'm looking for the same effect.
This is the code of the class:
Public Class LanguageProvider
Private Shared CurrentLanguage As Collection
Public Shared ReadOnly Property GetWord(ByVal Label As String)
Get
Try
Return CurrentLanguage.Item(Label)
Catch ex As Exception
Return Label
End Try
End Get
End Property
Public Shared Sub LoadLanguage(ByVal CultureId As Integer)
Dim SQLController As New SqlDataAccessController
SQLController.SQLSentence = "SELECT DNF_CULTURE_LANGUAGE_LABELS.LABEL, DNF_CULTURE_LANGUAGE_LABELS.VALUE FROM DNF_CULTURE_LANGUAGE_LABELS WHERE DNF_CULTURE_LANGUAGE_LABELS.CULTUREID = " & CultureId & " ORDER BY LABEL;"
SQLController.OpenConnection()
Dim dr As SqlClient.SqlDataReader
dr = SQLController.GetData()
CurrentLanguage = New Collection
While dr.Read()
CurrentLanguage.Add(dr.Item("Value"), dr.Item("Label"))
End While
dr.Close()
SQLController.CloseConnection()
End Sub
End Class
Function LoadLanguage must be called when the application loads, in order to access once to database.
After that property GetWord must access to the collection with the words and return the result. The problem is that the instances are not the same and the dictionary is not loaded when a class uses it.
Thanks.
Something along the lines of
Public Shared ReadOnly Property GetWord(ByVal Label As String)
Get
Try
If CurrentLanguage Is Nothing then
LoadLanguage(theDefaultCultureIdYouWant)
' Now CurrentLanguage is not Nothing any more,
' so LoadLanguage won't be called again
end if
Return CurrentLanguage.Item(Label)
Catch ex As Exception
Return Label
End Try
End Get
End Property
Of course, you have to provide a value for theDefaultCultureIdYouWant, depends on what you want to happen if the user just calls GetWord without explicitly mentioning a specific culture.