首页 > 解决方案 > 如何使我的 VB.NET 代码动态而不是静态?

问题描述

下面的代码给了我错误system.argumentexception an element with the same key already exists。当我在 Friend Sub Test 中使用以下行时:'Dim str_rootdirectory As String = Directory.GetCurrentDirectory() ' "C:\TEMP"它有效。有什么不同?

我的 VB.NET 代码:

Public Class Form1
    Public Sub recur_getdirectories(ByVal di As DirectoryInfo)
        For Each directory As DirectoryInfo In di.GetDirectories()
            'get each directory and call the module main to get the security info and write to json
            Call Module1.Main(directory.FullName)
            recur_getdirectories(directory)
        Next
    End Sub

    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        Dim rootDirectory As String = TextBox1.Text.Trim()
        Dim di As New DirectoryInfo(rootDirectory)

        'get directories recursively and work with each of them
        recur_getdirectories(di)
    End Sub
End Class

Public Module RecursiveEnumerableExtensions
    Iterator Function Traverse(Of T)(ByVal root As T, ByVal children As Func(Of T, IEnumerable(Of T)), ByVal Optional includeSelf As Boolean = True) As IEnumerable(Of T)
        If includeSelf Then Yield root
        Dim stack = New Stack(Of IEnumerator(Of T))()

        Try
            stack.Push(children(root).GetEnumerator())
            While stack.Count <> 0
                Dim enumerator = stack.Peek()
                If Not enumerator.MoveNext() Then
                    stack.Pop()
                    enumerator.Dispose()
                Else
                    Yield enumerator.Current
                    stack.Push(children(enumerator.Current).GetEnumerator())
                End If
            End While
        Finally
            For Each enumerator In stack
                enumerator.Dispose()
            Next
        End Try
    End Function
End Module

Public Module TestClass
    Function GetFileSystemAccessRule(d As DirectoryInfo) As IEnumerable(Of FileSystemAccessRule)
        Dim ds As DirectorySecurity = d.GetAccessControl()
        Dim arrRules As AuthorizationRuleCollection = ds.GetAccessRules(True, True, GetType(Security.Principal.NTAccount))

        For Each authorizationRule As FileSystemAccessRule In arrRules
            Dim strAclIdentityReference As String = authorizationRule.IdentityReference.ToString()
            Dim strInheritanceFlags As String = authorizationRule.InheritanceFlags.ToString()
            Dim strAccessControlType As String = authorizationRule.AccessControlType.ToString()
            Dim strFileSystemRights As String = authorizationRule.FileSystemRights.ToString()
            Dim strIsInherited As String = authorizationRule.IsInherited.ToString()
        Next

        ' This function should return the following values, because they should be mentoined in the JSON:
        ' IdentityReference = strAclIdentityReference
        ' InheritanceFlags = strInheritanceFlags
        ' AccessControlType = strAccessControlType
        ' FileSystemRights = strFileSystemRights
        ' IsInherited = strIsInherited

        Return ds.GetAccessRules(True, True, GetType(System.Security.Principal.NTAccount)).Cast(Of FileSystemAccessRule)()
    End Function

    Friend Sub Test(ByVal curDirectory As String)
        'Dim str_rootdirectory As String = Directory.GetCurrentDirectory() ' "C:\TEMP"
        Dim str_rootdirectory As String = curDirectory

        Dim di As DirectoryInfo = New DirectoryInfo(str_rootdirectory)

        Dim directoryQuery = RecursiveEnumerableExtensions.Traverse(di, Function(d) d.GetDirectories())

        Dim list = directoryQuery.Select(
            Function(d) New With {
                .directory = d.FullName,
                .permissions = {
                    GetFileSystemAccessRule(d).ToDictionary(Function(a) a.IdentityReference.ToString(), Function(a) a.FileSystemRights.ToString())
                }
            }
        )
        Dim json = JsonConvert.SerializeObject(list, Formatting.Indented)
        File.WriteAllText("ABCD.json", json)
    End Sub
End Module

Public Module Module1
    Public Sub Main(ByVal curDirectory As String)
        Console.WriteLine("Environment version: " & Environment.Version.ToString())
        Console.WriteLine("Json.NET version: " & GetType(JsonSerializer).Assembly.FullName)
        Console.WriteLine("")
        Try
            TestClass.Test(curDirectory)

        Catch ex As Exception
            Console.WriteLine("Unhandled exception: ")
            Console.WriteLine(ex)
            Throw
        End Try
    End Sub
End Module

我的示例文件夹结构:

文件夹: “C:\Temp”

权限: SecurityGroup-A 具有完全控制权,SecurityGroup-B 具有修改权限

文件夹: “C:\Temp\Folder_A”

权限: SecurityGroup-C 拥有完全控制权

但这只是两个文件夹的示例。实际上,它将运行数百个带有子文件夹的文件夹。因此 JSON 将扩展。

我的 json 输出期望:

[{
        "directory": "C:\\TEMP",
        "permissions": [{
                "IdentityReference": "CONTOSO\\SecurityGroup-A",
                "AccessControlType": "Allow",
                "FileSystemRights": "FullControl",
                "IsInherited": "TRUE"
            }, {
                "IdentityReference": "CONTOSO\\SecurityGroup-B",
                "AccessControlType": "Allow",
                "FileSystemRights": "Modify",
                "IsInherited": "False"
            }
        ]
    }, {
        "directory": "C:\\TEMP\\Folder_A",
        "permissions": [{
                "IdentityReference": "CONTOSO\\SecurityGroup-C",
                "AccessControlType": "Allow",
                "FileSystemRights": "Full Control",
                "IsInherited": "False"
            }
        ]
    }
]

标签: jsonvb.netjson.net

解决方案


您当前的 JSON 使用[*].permissions[*]对象的静态属性名称,因此无需尝试通过以下方式将它们的列表转换为具有变量键名的字典ToDictionary()

' This is not needed
.permissions = {
    GetFileSystemAccessRule(d).ToDictionary(Function(a) a.IdentityReference.ToString(), Function(a) a.FileSystemRights.ToString())
}

相反,将每个转换FileSystemAccessRule为一些适当的DTO以进行序列化。匿名类型对象很好地用于此目的:

Public Module DirectoryExtensions
    Function GetFileSystemAccessRules(d As DirectoryInfo) As IEnumerable(Of FileSystemAccessRule)
        Dim ds As DirectorySecurity = d.GetAccessControl()
        Dim arrRules As AuthorizationRuleCollection = ds.GetAccessRules(True, True, GetType(Security.Principal.NTAccount))
        Return arrRules.Cast(Of FileSystemAccessRule)()
    End Function
    
    Public Function SerializeFileAccessRules(ByVal curDirectory As String, Optional ByVal formatting As Formatting = Formatting.Indented)
        Dim di As DirectoryInfo = New DirectoryInfo(curDirectory)

        Dim directoryQuery = RecursiveEnumerableExtensions.Traverse(di, Function(d) d.GetDirectories())

        Dim list = directoryQuery.Select(
            Function(d) New With {
                .directory = d.FullName,
                .permissions = GetFileSystemAccessRules(d).Select(
                    Function(a) New With { 
                        .IdentityReference = a.IdentityReference.ToString(),
                        .AccessControlType = a.AccessControlType.ToString(),
                        .FileSystemRights = a.FileSystemRights.ToString(),
                        .IsInherited = a.IsInherited.ToString()
                    }
                )
            }
        )
        Return JsonConvert.SerializeObject(list, formatting)    
    End Function
End Module

Public Module RecursiveEnumerableExtensions
    ' Translated to vb.net from this answer https://stackoverflow.com/a/60997251/3744182
    ' To https://stackoverflow.com/questions/60994574/how-to-extract-all-values-for-all-jsonproperty-objects-with-a-specified-name-fro
    ' which was rewritten from the answer by Eric Lippert https://stackoverflow.com/users/88656/eric-lippert
    ' to "Efficient graph traversal with LINQ - eliminating recursion" https://stackoverflow.com/questions/10253161/efficient-graph-traversal-with-linq-eliminating-recursion
    Iterator Function Traverse(Of T)(ByVal root As T, ByVal children As Func(Of T, IEnumerable(Of T)), ByVal Optional includeSelf As Boolean = True) As IEnumerable(Of T)
        If includeSelf Then Yield root
        Dim stack = New Stack(Of IEnumerator(Of T))()

        Try
            stack.Push(children(root).GetEnumerator())
            While stack.Count <> 0
                Dim enumerator = stack.Peek()
                If Not enumerator.MoveNext() Then
                    stack.Pop()
                    enumerator.Dispose()
                Else
                    Yield enumerator.Current
                    stack.Push(children(enumerator.Current).GetEnumerator())
                End If
            End While
        Finally
            For Each enumerator In stack
                enumerator.Dispose()
            Next
        End Try
    End Function
End Module

此处演示小提琴(不幸的是,由于客户端代码的安全限制,它在https://dotnetfiddle.net上不起作用,但应该可以完全信任运行)。


推荐阅读