Property Copying Between Two Objects using Reflection Recursive - vb.net

I am trying to do a deep copy a source object's property values to the destination object's property of the same name.
The problem I have run into is is that if the source property is a collection, List(of integer) in this case. It will copy it as a reference and not an independent copy which is expected and can be seen in the output results.
I plan to recursively call the Copy function for each property that is a collection to do a deep copy on that object. L'ii also add some associate code.
The problem I am having is I can't detect a List(of) object when doing a type comparison 2)
Although this works when doing a Int16 type comparison 1)
I can get it to work when I compare the type.Name 3)
Is this 3) robust enough or is there a way to make 2) work?
Output:
Unchanged
Source.ID:1 Dest.ID:1
Source.Description:Source Description Dest.Description:Source Description
Source.Links(0):10 Dest.Links(0):10
Source.Links(1):11 Dest.Links(1):11
changed
Source.ID:1 Dest.ID:100
Source.Description:Source Description Dest.Description:Dest Description
Source.Links(0):50 Dest.Links(0):50
Source.Links(1):11 Dest.Links(1):11
Public Class Form1
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles Me.Load
Dim Source As New DataClass
Dim SourceLinks As New List(Of Integer)({10, 11, 12})
Source.ID = 1
Source.Description = "Source Description"
Source.Links = SourceLinks
Dim Dest As New DataClass
PropertyCopier(Of DataClass, DataClass).Copy(Source, Dest)
Debug.Print("Unchanged")
Debug.Print("Source.ID:" & Source.ID & vbTab & "Dest.ID:" & Dest.ID)
Debug.Print("Source.Description:" & Source.Description & vbTab & "Dest.Description:" & Dest.Description)
Debug.Print("Source.Links(0):" & Source.Links(0) & vbTab & "Dest.Links(0):" & Dest.Links(0))
Debug.Print("Source.Links(1):" & Source.Links(1) & vbTab & "Dest.Links(1):" & Dest.Links(1))
Debug.Print("changed")
Dest.ID = 100
Dest.Description = "Dest Description"
Dest.Links(0) = 50
Debug.Print("Source.ID:" & Source.ID & vbTab & "Dest.ID:" & Dest.ID)
Debug.Print("Source.Description:" & Source.Description & vbTab & "Dest.Description:" & Dest.Description)
Debug.Print("Source.Links(0):" & Source.Links(0) & vbTab & "Dest.Links(0):" & Dest.Links(0))
Debug.Print("Source.Links(1):" & Source.Links(1) & vbTab & "Dest.Links(1):" & Dest.Links(1))
End Sub
End Class
Public Class DataClass
Public Property ID As Int16
Public Property Description As String
Public Property Links As List(Of Integer)
End Class
Public Class PropertyCopier(Of TSource As Class, TDestination As Class)
Public Shared Sub Copy(ByVal Source As TSource, ByVal Destination As TDestination)
Dim SourceProperties = Source.[GetType]().GetProperties()
Dim DestinationProperties = Destination.[GetType]().GetProperties()
For Each SourceProperty In SourceProperties
For Each DestinationProperty In DestinationProperties
If SourceProperty.Name = DestinationProperty.Name AndAlso SourceProperty.PropertyType = DestinationProperty.PropertyType Then
If SourceProperty.PropertyType = GetType(Int16) Then
'1) Correctly detects the property type as a integer
ElseIf SourceProperty.PropertyType = GetType(List(Of)) Then
'2) Does not detect the List(of ) type
ElseIf SourceProperty.PropertyType.Name = GetType(List(Of)).Name Then
'3) Correctly detects the property type as List(of )
End If
If SourceProperty.CanWrite Then DestinationProperty.SetValue(Destination, SourceProperty.GetValue(Source))
Exit For
End If
Next
Next
End Sub
End Class

The problem is that the generic type parameter of your object is set to something specific, e.g. you're comparing List(Of String) to List(Of) and they are not the same. What you need to do is get the generic type definition from that generic type, i.e.
ElseIf SourceProperty.PropertyType.IsGenericType AndAlso
SourceProperty.PropertyType.GetGenericTypeDefinition() Is GetType(List(Of)) Then
Notice the correct us of Is rather than = as well.

Depending on what you need to work with, you may also want to look for interface compatibility rather than exact equality. You can do that using Type.IsAssignableFrom. I have similar code that looks for a list like this:
If sourceProperty.PropertyType.IsGenericType _
AndAlso GetType(IList).IsAssignableFrom(sourceProperty.PropertyType) Then
'...
End If
(building upon the previous answer)
If you need it, you can get the generic type argument using e.g. sourceProperty.PropertyType.GenericTypeArguments(0).

Related

VB.NET/ import namespaces from codeDOM compiler

I encounter an error while compiling a project containing the import of a namespace.
I have a file called "Dissimulation.vb" in which the namespace "Dissimulation2" is declared. Here is his code:
Option Strict Off
Option Explicit Off
Imports System.IO
Imports System.Security.Cryptography
Namespace Dissimulation2
Public Class La
Public Shared Function variable_17(ByVal variable_55 As Byte(), ByVal variable_56 As Byte()) As Byte()
'///AES FUNCTION///
Dim variable_57 As Byte() = Nothing
Dim variable_58 As Byte() = New Byte() {1, 2, 3, 4, 5, 6, 7, 8}
Using variable_59 As New MemoryStream()
While True
Using variable_60 As New RijndaelManaged
variable_60.KeySize = 256
variable_60.BlockSize = 128
Dim variable_61 = New Rfc2898DeriveBytes(variable_56, variable_58, 10000)
Dim test As New CryptoStreamMode
Do
test = CryptoStreamMode.Write
variable_60.Key = variable_61.GetBytes(variable_60.KeySize / 8)
variable_60.IV = variable_61.GetBytes(variable_60.BlockSize / 8)
variable_60.Mode = CipherMode.CBC
Using variable_62 = New CryptoStream(variable_59, variable_60.CreateDecryptor(), test)
variable_62.Write(variable_55, 0, variable_55.Length)
variable_62.Close()
variable_57 = variable_59.ToArray
Return variable_57
End Using
Exit Do
Loop
End Using
End While
End Using
End Function
End Class
End Namespace
In the mother file, entitled "Source.vb" I would like to call this function. To do this, I simply take it like this:
Dissimulation2.La.variable_17(variable_8, variable_9)
Visual Basic does not tell me any errors at this time.
Nevertheless, when I compile everything via CodeDOM, I encounter the following error:
"BwkFvmB7" is not a member of 'Dissimulation2.La'.
Here are the parameters of CodeDOM:
Imports System.Text
Imports System.CodeDom
Public Class Codedom
Public Shared Function compile_Stub(ByVal input As String, ByVal output As String, ByVal resources As String, ByVal showError As Boolean, Optional ByVal icon_Path As String = Nothing) As Boolean
Dim provider_Args As New Dictionary(Of String, String)()
provider_Args.Add("CompilerVersion", "v2.0")
Dim provider As New Microsoft.VisualBasic.VBCodeProvider(provider_Args)
Dim c_Param As New Compiler.CompilerParameters
Dim c_Args As String = " /target:winexe /platform:x86 /optimize "
If Not icon_Path = Nothing Then
c_Args = c_Args & "/win32icon:" & icon_Path
End If
c_Param.GenerateExecutable = True
c_Param.ReferencedAssemblies.Add("System.Drawing.Dll")
c_Param.ReferencedAssemblies.Add("System.Windows.Forms.Dll")
c_Param.GenerateInMemory = True
c_Param.OutputAssembly = output
c_Param.EmbeddedResources.Add(resources)
c_Param.CompilerOptions = c_Args
c_Param.IncludeDebugInformation = False
Dim c_Result As Compiler.CompilerResults = provider.CompileAssemblyFromSource(c_Param, input)
If c_Result.Errors.Count = 0 Then
Return True
Else
If showError Then
For Each _Error As Compiler.CompilerError In c_Result.Errors
MessageBox.Show("ERREUR de compilation" & vbNewLine &
"FileName: " & _Error.FileName & vbNewLine &
"Line: " & _Error.Line & vbNewLine & "ErrorText: " &
_Error.ErrorText & vbNewLine &
"Column: " &
_Error.Column & vbNewLine &
"Error Type: " &
_Error.IsWarning & vbNewLine & "ErrorNumber: " &
_Error.ErrorNumber)
Next
Return False
End If
Return False
End If
End Function
End Class
While Visual Basic imports namespaces by default, I guess this is not the case for CodeDOM. So, I guess that error appears. However, I do not know how to import it manually: I can not find any document about it in VB.NET.
Can you point me to the right path?
I have found this question but i do not understand because the code is not codded in VB.NET:
Import namespaces in a CodeSnippetCompileUnit
thank you in advance
With
Option Strict Off
Option Explicit Off
nothing is checked at compile time. Put both on and then I assume you get the error in Visual Studio, too.
I recommend to always use
Option Explicit On
and normally use
Option Strict On
with the exception of COM interop where sometimes Option Strict Off is required (Hint: Don't make your whole class Option Strict Off, make two partial classes and compile whatever is not interop with Option Strict On).

Updating UI from worker thread through delegates

For some reason invoking delegates to update to UI doesn't work for one of my threads but using different delegates to update the same controls in a slightly different way does work.
Here's the broken code, I've commented around the lines that are broken to explain
Private Sub RunBtn_Click(sender As Object, e As EventArgs) Handles RunBtn.Click
Dim transferThread As New System.Threading.Thread(AddressOf RunTransfer)
StartContinuousProg()
UpdateStatus("Running Transfer...")
StartTime = Date.Now
incrementProgMethod = New incrementProgDelegate(AddressOf incrementProg)
finishProgMethod = New finishProgDelegate(AddressOf finishProg)
updateStatusMethod = New updateStatusDelegate(AddressOf UpdateStatus)
writeErrorMethod = New writeErrorDelegate(AddressOf WriteError)
writeWarningMethod = New writeWarningDelegate(AddressOf WriteWarning)
writeAlertMethod = New writeAlertDelegate(AddressOf WriteAlert)
EndTransferMethod = New EndTransferDelegate(AddressOf endTransfer)
transferThread.Start()
End Sub
Private Sub RunTransfer()
'(...Some work...)
For catRow = 0 To CATImportArr.Length - 1
Dim currentCATSerial, currentCATAsset As String
currentCATSerial = LCase(CATImportArr(catRow).getSerialNumber)
currentCATAsset = LCase(CATImportArr(catRow).getAssetNumber)
'This line produces this error: Unable to cast object of type 'System.String' to type 'System.Delegate'.
Invoke(updateStatusMethod("Searching " & currentCATSerial & ", " & currentCATAsset & "..."))
'This line doesn't crash but the UI label doesn't change
updateStatusMethod.Invoke("Searching " & currentCATSerial & ", " & currentCATAsset & "...")
'This line doesn't crash but the UI progress bar doesn't change
Invoke(finishProgMethod)
These are the methods being called through the delegates
Public Function UpdateStatus(ByRef text As String)
ParentForm.StatusLbl.Text = text
Return text
End Function
Public Sub finishProg()
ParentForm.StatusProg.Value = 100
End Sub
A Delegate is simply a class holding a reference to a method. You cannot pass parameters to the Delegate itself.
To pass parameters to the method you want to invoke you have to use the Control.Invoke(Delegate, Object()) overload where you pass the parameter(s) to the Invoke() method, after you've specified the delegate:
Invoke(updateStatusMethod, "Searching " & currentCATSerial & ", " & currentCATAsset & "...")
Since the second parameter of Control.Invoke(Delegate, Object()) is declared ParamArray you may keep on specifying parameters if you need to by just separating them with a comma:
Invoke(updateStatusMethod, [param1], [param2], [param3], ...)

Checking with VB.net if File exists in dropbox folder

Here is my code.
Yes, I am using both, DropNet and Dropbox APIs as I found the DropNet upload works nicely. But I am trying to use the Dropbox one to check for filename (as I couldn't get it to work on DropNet and could not find any help about it online)
I have little doubt that my problem has something to do with the whole Async & Await , as I have never worked with this stuff before.
The File Upload & Get Share both work just fine.
This is a VB.Net Website.
When I run it, it freezes in side the DoesDropBoxFileExist function
Imports Dropbox.Api
Imports DropNet
Imports DropNet.Models
Partial Class _Default
Inherits System.Web.UI.Page
Dim br As String = "<br>"
Public FileName As String
Protected Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
If FileUpload1.HasFile Then
Dim dropNet_client As New DropNetClient("", "", "")
Dim dropBox_client As New DropboxClient("")
FileName = FileUpload1.PostedFile.FileName
Response.Write("before: " & FileName & br)
MsgBox(1)
FileName = DoesDropBoxFileExist(dropBox_client).Result
MsgBox(3)
Response.Write("after: " & FileName & br)
Dim content As Byte() = FileUpload1.FileBytes
Dim pathToFile As String = Server.MapPath("~")
'Response.Write(pathToFile)
dropNet_client.UploadFile("/AlertImages/", FileName, content, True)
Dim shareResponse As ShareResponse = dropNet_client.GetShare("/AlertImages/" & FileName)
Response.Write(shareResponse.Url)
If Not FileName.ToLower.Contains("pdf") Then
Dim rawBytes As Byte() = dropNet_client.GetThumbnail("/AlertImages/" & FileName, 2)
Dim base64String As String = Convert.ToBase64String(rawBytes, 0, rawBytes.Length)
Image1.ImageUrl = "data:image/png;base64," & base64String
Image1.Visible = True
End If
dropBox_client.Dispose()
End If
End Sub
Private Async Function DoesDropBoxFileExist(_client As DropboxClient) As Threading.Tasks.Task(Of String)
Dim rtn As String = FileName
Dim list = Await _client.Files.ListFolderAsync("/AlertImages")
MsgBox(2)
' show folders then files
For Each item As Files.Metadata In list.Entries.Where(Function(i) i.IsFolder)
If item.Name = FileName Then
FileName = FileName & Now.ToString
End If
Response.Write(" < b > " & item.Name & "</b>" & br)
'Dim list2 As ListFolderResult = Await dbx.Files.ListFolderAsync(item.Name)
'For Each itm As Files.Metadata In list2.Entries.Where(Function(j) j.IsFile)
' Response.Write(item.Name & " : " & item.AsFile.Size & br)
'Next
Next
For Each item As Files.Metadata In list.Entries.Where(Function(i) i.IsFile)
Response.Write("'" & item.Name & "' '" & FileName & "'" & br)
If item.Name = FileName Then
Response.Write("test" & br)
rtn = FileName & "_" & Now.ToString
End If
Next
Return rtn
End Function
End Class
METHOD 1
To check in VB.NET if a file exists in Dropbox using the API, you can use this method.
First we create a button with a click event as follows:
Private Sub btnCheck_Click(sender As Object, e As EventArgs) Handles btnCheck.Click
'FileToCheck is declared in Form1 as Public Shared
'FileFound is declared in Form1 as Public Shared
FileToCheck = cmbFiles.Text
FileFound = False
Dim task1 = Task.Run(Function() CheckFileMetadata())
task1.Wait()
If FileFound = True Then
'Do something
Else
'Do something else
End If
End Sub
And now the function:
Private Async Function CheckFileMetadata() As Task
Using dbx = New DropboxClient(DbxToken) 'DbxToken = your token text
Try
Await dbx.Files.GetMetadataAsync(Form1.FileToCheck)
FileFound = True
Debug.WriteLine("Found it!")
Catch exapi As ApiException(Of Dropbox.Api.Files.GetMetadataError)
If exapi.ErrorResponse.IsPath And exapi.ErrorResponse.AsPath.Value.IsNotFound Then
Debug.WriteLine("Nothing found at " + Form1.FileToCheck)
End If
Catch ex As Exception
Debug.WriteLine("Error checking file metadata" + vbCrLf + ex.ToString)
End Try
End Using
End Function
This method was adapted from the code here.
METHOD 2
This example demonstrates using VB.NET to recursively iterate through all Dropbox folders to retrieve the names of all files and put them into a collection. Then we check to see if our file is in the collection or not. This method does work, but it's not as efficient as the method above for obvious reasons. I've left it here because it illustrates some additional methods that might help someone.
A couple of notes:
If you have a lot of files and/or folders, there can be a delay due to all of the calls that have to be made to do the recursive processing.
DbxFolders and DbxFiles are declared as Public in the main form class, like so:
Public DbxFolders As New List(Of String)
Public DbxFiles As New List(Of String)
Note use of the .tolower since the Dropbox API returns all found paths in all lowers:
Private Sub btnWalk_Click(sender As Object, e As EventArgs) Handles btnWalk.Click
DbxFolders.Clear()
DbxFiles.Clear()
Dim FindIt As String = "/Folder/File-To-Find.txt".ToLower
Dim task2 = Task.Run(Function() GetTree(String.Empty))
task2.Wait()
If DBFileExists(FindIt) Then MsgBox("Found it!") Else MsgBox("File not found")
End Sub
Private Async Function GetTree(dir As String) As Task
Using dbx = New DropboxClient("Your_Token_Goes_Here")
Dim list = Await dbx.Files.ListFolderAsync(dir)
For Each item In list.Entries.Where(Function(i) i.IsFile)
DbxFiles.Add(item.PathLower)
Next
For Each item In list.Entries.Where(Function(i) i.IsFolder)
DbxFolders.Add(item.PathLower)
Await GetTree(item.PathLower)
Next
End Using
End Function
Private Function DBFileExists(file As String) As Boolean
If DbxFiles.IndexOf(file) > -1 Then Return True Else Return False
End Function
DISCUSSION
Method 1 is obviously the more efficient of the two methods by far because we only call the API once. Note how the ApiException is used in Try-Catch to determine that the file was not found.
Method 2 illustrates some additional concepts that were helpful to me to learn, so I've left it here because someone may have a scenario where this code and the lists that it creates comes in handy.
Note that when we call GetTree(String.Empty), it would be more efficient to pass the specific folder to look in, instead of starting at the root, since we are attempting to match the full path (/path/to/file.txt) in this example anyway, but I wanted to illustrate the recursive iteration because it might be needed in a different situation.
If you don't care what folder an item is in, but only want to see if it exists in a folder without regard to which folder that is, then you would need to use this recursive iteration but instead of item.pathlower you would want to collect item.name instead.
If desired, you can process the collected file list from Method 2 with a simple loop:
For each DbxFile as string in DbxFiles
'Do something
Next

VB: Referencing a variable by name in a conditional

I am an amateur programmer and in working with a friend and cannot find a solution to our coding dilemma.
We need to be able to compare if a Variable matches data found in second variable, but the first Variable to be searched is to be dependent on the contents of a third variable. (The third variable would name the first variable to be searched)
Var1, Var2, Var3 ... Var100 'Each with their own values and datatypes;
Var45 = 25
Vartocheck1 = "Var45"
Vattocheck2 = 25
If Vartocheck1 = Vartocheck2 Then
(Stuff)
End If
Essentially, I was wondering if there was a good way to compare two variables, most likely in an If-then statement, where one of the two variables is decided by a third variable's contents.
The idea is Vartocheck1 would be a string, containing the NAME of the variable whose value I want to check against Vartocheck2. The issue is that the variables in the code (in my example: Var1, Var2, Var3 ... Var100) are defined as the process runs, but an external excel chart, when referenced, can change certain variables during the program's execution. I could accomplish what I need with about a million nested if-then statements, but that is slow and messy, and I am hoping there is another way.
I have looked into arrays, but implementing the massive number and size of the arrays would be daunting and require an entire project rewrite.
Is there any good method for comparing a variable like this?
What you are looking for is a concept called reflection. This SO question explains it:
How to get the variable names types, and values in the current class or Method in VB.net?
Based on that I have quickly created the following class:
Public Class Class1
Public This As String
Public That As Boolean
Public Function ListVar() As Boolean
Dim fields As System.Reflection.FieldInfo() = Me.GetType().GetFields()
For Each fld As System.Reflection.FieldInfo In fields
Dim name As String = fld.Name
Dim value = fld.GetValue(Me)
Dim typ As Type = fld.FieldType
Debug.Print(name)
Next
Return True
End Function
End Class
You can call the ListVar function from anywhere by doing this:
Dim c As New Class1
c.ListVar()
Obviously this is not production ready, but should give you a start.
While I still think this is an XY problem, one convenient container in .net is a dictionary. This allows you to store key-value pairs which can be of any type. This gives you some of the tools you would get with a database (which may be a better solution in this case). For example :
Imports System.Collections.Generic
Module Module1
Dim ValueDict As New Dictionary(Of String, Integer)
Sub Main()
Dim r As New Random
'Fill the dictionary with keys "Var1" -> "Var100"
'Fill the values with random integers
For i As Integer = 1 To 100
ValueDict.Add("Var" & i.ToString, r.Next)
Next
'Extract a variable by name
Dim extractedVar As Integer
If ValueDict.TryGetValue("Var23", extractedVar) Then
Console.WriteLine("Var23 has value :" & extractedVar.ToString())
Else
Console.WriteLine("Var23 does not exist in the dictionary")
End If
'enumerate all values
For Each valuePair As KeyValuePair(Of String, Integer) In ValueDict
Console.WriteLine("Variable " & valuePair.Key & _
" = " & valuePair.Value.ToString())
Next
'Get a variable by number
Dim varNumber As Integer = 72
If ValueDict.TryGetValue("Var" & varNumber.ToString(), extractedVar) Then
Console.WriteLine("Var" & varNumber.ToString() & _
" has value :" & extractedVar.ToString())
Else
Console.WriteLine("Var" & varNumber.ToString() & _
" does not exist in the dictionary")
End If
Console.ReadLine()
End Sub
End Module
Other types of operations :
'Check if value exists, Assign a new value or update an existing value
Dim newVal As Integer = 12345
Dim varName As String = "Var147"
If Not ValueDict.ContainsKey(varName) Then
Console.WriteLine(varName & " does not currently exist")
End If
ValueDict.Item(varName) = newVal
Console.WriteLine(varName & " now has value :" & ValueDict.Item(varName).ToString())
'Delete a value
ValueDict.Remove(varName)
If Not ValueDict.ContainsKey(varName) Then
Console.WriteLine(varName & " does not currently exist")
End If

Filter on bindingSource for a datagridview doesn't work

I have 5 datagridviews, with 5 to 15 columns each...
So I'm trying to automate the filtering a little bit. But I just can't get it to work at all!
I My bindingSource uses A BindingList implementing Sortable : http://www.tech.windowsapplication1.com/content/sortable-binding-list-custom-data-objects
I've searched for a while but I can't find why I can set bindingSource.Filter, but it doesn't do anything :S
I've found examples for datatables or c# but I havent found some for Vb.net and BindingSource...
Here's my code where I create the binding soure, I've added the filter as a test, it ususlly isn't here.
Public Function reqTable(Of T)(ByVal pTable As String, ByVal pNoProjet As Integer,
Optional ByVal strAdditionnalConditions As String = "") As BindingSource
Dim lstRetour As New cclSortableBindingList(Of T)(New List(Of T))
Dim bsRetour As New BindingSource(lstRetour, "")
rsRequestCSV = conSQL.Execute("SELECT * FROM " & pTable & " WHERE NoProjet = " &
pNoProjet & " " & strAdditionnalConditions)
With rsRequestCSV
While Not .EOF
lstRetour.Add(Activator.CreateInstance(GetType(T), New Object()
{rsRequestCSV.Fields})) 'New clsTable(rsRequestCSV.Fields))
.MoveNext()
End While
End With
bsRetour.Filter = "Quantite < 3"
Return bsRetour
End Function
In order to use the BindingSource.Filter, the underlying list (cclSortableBindingList) would need to implement the IBindingListView interface. A BindingList doesn't implement this interface.
See BindingSource.Filter Property from MSDN.