我最近启动了一个项目,需要通过DirectoryServices与LDAP进行一些集成。我已经在其他应用程序中完成了这个,所以我进入其中一个看看我是如何做到的 - 为什么重新发明轮子呢?嗯,虽然这个轮子工作,它是几年前开发的,有点气味(它是木制的,牢固地附着在以前的车辆上,难以修复并产生可能颠簸的行程)。
所以我想,这是重构这只小狗的最佳时机,让它更便携,可重复使用,更可靠,更容易配置等等。现在这很好,花花公子,但后来我开始觉得有点不知所措开始。它应该是一个单独的图书馆吗?应该如何配置?它应该使用IoC吗? DI?
所以我的[公认的主观]问题是这个 - 给出一个相对较小但非常有用的类,如下所示,重构它的好方法是什么?您有什么问题,以及如何决定实施或不实施?您在哪里绘制关于配置灵活性的界限?
[注意:请不要太抨击这段代码好吗?它是很久以前写的,并且已经很好地融入了内部应用程序。]
Public Class AccessControl
Public Shared Function AuthenticateUser(ByVal id As String, ByVal password As String) As Boolean
Dim path As String = GetUserPath(id)
If path IsNot Nothing Then
Dim username As String = path.Split("/")(3)
Dim userRoot As DirectoryEntry = New DirectoryEntry(path, username, password, AuthenticationTypes.None)
Try
userRoot.RefreshCache()
Return True
Catch ex As Exception
Return False
End Try
Else
Return False
End If
End Function
Private Shared Function GetUserPath(ByVal id As String) As String
Dim root As DirectoryEntry = New DirectoryEntry("LDAP://XXXXX/O=YYYY", String.Empty, String.Empty, AuthenticationTypes.None)
Dim searcher As New DirectorySearcher
Dim results As SearchResultCollection
Dim result As SearchResult
Try
With searcher
.SearchRoot = root
.PropertiesToLoad.Add("cn")
.Filter = String.Format("cn={0}", id)
results = .FindAll()
End With
If results.Count > 0 Then
result = results(0)
Return result.Path.ToString()
Else
Return Nothing
End If
Catch ex As Exception
Return Nothing
End Try
End Function
Public Shared Function GetUserInfo(ByVal id As String) As UserInfo
Dim root As DirectoryEntry = New DirectoryEntry("LDAP://XXXXX/O=YYYY", String.Empty, String.Empty, AuthenticationTypes.None)
Dim searcher As New DirectorySearcher
Dim results As SearchResultCollection
Dim props() As String = {"id", "sn", "mail", "givenname", "container", "cn"}
Try
With searcher
.SearchRoot = root
.PropertiesToLoad.AddRange(props)
.Filter = String.Format("cn={0}", id)
results = .FindAll()
End With
If results.Count > 0 Then
Dim properties As PropertyCollection = results(0).GetDirectoryEntry().Properties
Dim user As New UserInfo(properties("id").Value)
user.EmailAddress = properties("mail").Item(0).ToString
user.FirstName = properties("givenname").Item(0).ToString
user.LastName = properties("sn").Item(0).ToString
user.OfficeLocation = properties("container").Item(0).ToString
Return user
Else
Return New UserInfo
End If
Catch ex As Exception
Return Nothing
End Try
End Function
Public Shared Function IsMemberOfGroup(ByVal id As String, ByVal group As String) As Boolean
Dim root As DirectoryEntry = New DirectoryEntry("LDAP://XXXXX/O=YYYY", String.Empty, String.Empty, AuthenticationTypes.None)
Dim searcher As New DirectorySearcher
Dim results As SearchResultCollection
Dim result As SearchResult
Dim props() As String = {"cn", "member"}
Try
With searcher
.SearchRoot = root
.PropertiesToLoad.AddRange(props)
.Filter = String.Format("cn={0}", group)
results = .FindAll()
End With
If results.Count > 0 Then
For Each result In results
Dim members As PropertyValueCollection = result.GetDirectoryEntry().Properties("member")
Dim member As String
For i As Integer = 0 To members.Count - 1
member = members.Item(i).ToString
member = member.Substring(3, member.IndexOf(",") - 3).ToLowerInvariant
If member.Contains(id.ToLowerInvariant) Then Return True
Next
Next
End If
Return False
Catch ex As Exception
Return False
End Try
End Function
Public Shared Function GetMembersOfGroup(ByVal group As String) As List(Of String)
Dim groupMembers As New List(Of String)
Dim root As DirectoryEntry = New DirectoryEntry("LDAP://XXXXX/O=YYYY", String.Empty, String.Empty, AuthenticationTypes.None)
Dim searcher As New DirectorySearcher
Dim results As SearchResultCollection
Dim result As SearchResult
Dim props() As String = {"cn", "member"}
Try
With searcher
.SearchRoot = root
.PropertiesToLoad.AddRange(props)
.Filter = String.Format("cn={0}", group)
results = .FindAll()
End With
If results.Count > 0 Then
For Each result In results
Dim members As PropertyValueCollection = result.GetDirectoryEntry().Properties("member")
Dim member As String
For i As Integer = 0 To members.Count - 1
member = members.Item(i).ToString
member = member.Substring(3, member.IndexOf(",") - 3).ToLowerInvariant
groupMembers.Add(member)
Next
Next
End If
Catch ex As Exception
Return Nothing
End Try
Return groupMembers
End Function
End Class
澄清:
- 有一个单独的用户类(简单poco)
- 没有组类,因为现在使用的所有内容都是id列表,虽然可以添加
答案 0 :(得分:2)
我认为修改异常处理也很重要。我在上面的方法中看到的模式是:
Try
...
Catch ex As Exception
Return False
End Try
上面的代码基本上是隐藏(吞咽)它的异常。这可能是最初实现的,因为由于找不到用户而抛出了某些类型的异常,并且返回False或Nothing可能是合适的。但是,您可能在应用程序中获得了其他类型的异常(例如OutOfMemoryException等)。
我建议只捕获您可能想合法返回false / Nothing的特定类型的异常。对于其他人,请将异常冒泡,或将其记录在最低限度。
有关异常处理的其他提示,请阅读this helpful post。
答案 1 :(得分:2)
以下是重构代码示例的示例:
Public Class AccessControl
Public Shared Function AuthenticateUser(ByVal id As String, ByVal password As String) As Boolean
Dim path As String
Dim username As String
Dim userRoot As DirectoryEntry
path = GetUserPath(id)
If path.Length = 0 Then
Return False
End If
username = path.Split("/")(3)
userRoot = New DirectoryEntry(path, username, password, AuthenticationTypes.None)
Try
userRoot.RefreshCache()
Return True
Catch ex As Exception
' Catching Exception might be accepted way of determining user is not authenticated for this case
' TODO: Would be better to test for specific exception type to ensure this is the reason
Return False
End Try
End Function
Private Shared Function GetUserPath(ByVal id As String) As String
Dim results As SearchResultCollection
Dim propertiesToLoad As String()
propertiesToLoad = New String() {"cn"}
results = GetSearchResultsForCommonName(id, propertiesToLoad)
If results.Count = 0 Then
Return String.Empty
Else
Debug.Assert(results.Count = 1)
Return results(0).Path
End If
End Function
Public Shared Function GetUserInfo(ByVal id As String) As UserInfo
Dim results As SearchResultCollection
Dim propertiesToLoad As String()
propertiesToLoad = New String() {"id", "sn", "mail", "givenname", "container", "cn"}
results = GetSearchResultsForCommonName(id, propertiesToLoad)
If results.Count = 0 Then
Return New UserInfo
End If
Debug.Assert(results.Count = 1)
Return CreateUser(results(0).GetDirectoryEntry().Properties)
End Function
Public Shared Function IsMemberOfGroup(ByVal id As String, ByVal group As String) As Boolean
Dim allMembersOfGroup As List(Of String)
allMembersOfGroup = GetMembersOfGroup(group)
Return allMembersOfGroup.Contains(id.ToLowerInvariant)
End Function
Public Shared Function GetMembersOfGroup(ByVal group As String) As List(Of String)
Dim results As SearchResultCollection
Dim propertiesToLoad As String()
propertiesToLoad = New String() {"cn", "member"}
results = GetSearchResultsForCommonName(group, propertiesToLoad)
Return ConvertMemberPropertiesToList(results)
End Function
Private Shared Function GetStringValueForPropertyName(ByVal properties As PropertyCollection, ByVal propertyName As String) As String
Return properties(propertyName).Item(0).ToString
End Function
Private Shared Function CreateUser(ByVal userProperties As PropertyCollection) As UserInfo
Dim user As New UserInfo(userProperties("id").Value)
With user
.EmailAddress = GetStringValueForPropertyName(userProperties, "mail")
.FirstName = GetStringValueForPropertyName(userProperties, "givenname")
.LastName = GetStringValueForPropertyName(userProperties, "sn")
.OfficeLocation = GetStringValueForPropertyName(userProperties, "container")
End With
Return user
End Function
Private Shared Function GetValueFromMemberProperty(ByVal member As String) As String
Return member.Substring(3, member.IndexOf(",") - 3).ToLowerInvariant
End Function
Private Shared Function ConvertMemberPropertiesToList(ByVal results As SearchResultCollection) As List(Of String)
Dim result As SearchResult
Dim memberProperties As PropertyValueCollection
Dim groupMembers As List(Of String)
groupMembers = New List(Of String)
For Each result In results
memberProperties = result.GetDirectoryEntry().Properties("member")
For i As Integer = 0 To memberProperties.Count - 1
groupMembers.Add(GetValueFromMemberProperty(memberProperties.Item(i).ToString))
Next
Next
Return groupMembers
End Function
Private Shared Function GetSearchResultsForCommonName(ByVal commonName As String, ByVal propertiesToLoad() As String) As SearchResultCollection
Dim results As SearchResultCollection
Dim searcher As New DirectorySearcher
With searcher
.SearchRoot = GetDefaultSearchRoot()
.PropertiesToLoad.AddRange(propertiesToLoad)
.Filter = String.Format("cn={0}", commonName)
results = .FindAll()
End With
Return results
End Function
Private Shared Function GetDefaultSearchRoot() As DirectoryEntry
Return New DirectoryEntry("LDAP://XXXXX/O=YYYY", String.Empty, String.Empty, AuthenticationTypes.None)
End Function
End Class
我认为你可以通过提取常量等来进一步解决这个问题,但这会消除大部分重复,等等。让我知道你的想法。
方式,为时已晚,我意识到......但也想解决你提出的一些问题。见http://chrismelinn.wordpress.com/2011/06/18/questions-before-refactoring-2/
答案 2 :(得分:1)
我要做的第一件事是删除任何重复。删除常用功能以分离方法。
此外,按照每个类的思路,应该只有一个角色/责任 - 您可能想要创建单独的用户和组类。
这里有一个常见的重构目录:
http://www.refactoring.com/catalog/index.html
如果您因为测试原因而希望交换不同的类别,那么您应该只考虑控制反转...
答案 3 :(得分:1)
这可能不是最重要的重构,但我是早期回归的忠实粉丝。所以,例如,你在哪里:
If results.Count > 0 Then
Dim properties As PropertyCollection = results(0).GetDirectoryEntry().Properties
Dim user As New UserInfo(properties("id").Value)
user.EmailAddress = properties("mail").Item(0).ToString
user.FirstName = properties("givenname").Item(0).ToString
user.LastName = properties("sn").Item(0).ToString
user.OfficeLocation = properties("container").Item(0).ToString
Return user
Else
Return New UserInfo
End If
我会用,而不是:
If results.Count == 0 Then Return New UserInfo
Dim properties As PropertyCollection = results(0).GetDirectoryEntry().Properties
Dim user As New UserInfo(properties("id").Value)
user.EmailAddress = properties("mail").Item(0).ToString
user.FirstName = properties("givenname").Item(0).ToString
user.LastName = properties("sn").Item(0).ToString
user.OfficeLocation = properties("container").Item(0).ToString
Return user
缩进意味着复杂性,并且在空结果案例的特殊处理中没有足够的复杂性来保证8行缩进。有一点,删除缩进实际上可以隐藏真正的复杂性,所以不要过于强调这个规则,但对于提供的代码,我肯定会使用早期的返回。
答案 4 :(得分:0)
立即跳出来的第一件事是,有很多涉及用户的功能似乎与您创建的用户类无关。
我会尝试以下一些方法: