This project wraps up AD functionality, but can be easily extended to expand a user object to contain properties from other systems. For example, say you're running an ERP application and you have certain properties you would like to expose to your intranet application, in addition, your application is authenticating and consuming information from AD. You could just create multiple objects to call information from the various systems and then display the information in the client. Here we will create a single user object with properties that are exposed via the object, by combining all the user properties in our custom object, the client then only interacts with our object. If any changes happen to the backend for user properties or we want to expose information from other sources, we can then change our object class, without having to change the various consumers. You can then wrap the object in a Web service to extend it further.
Defining the User Class
The first thing we're going to do is create a user object. For this tutorial we're just exposing AD properties, so we'll define some of the properties that we'll be pulling from AD
First our private members:
Public Class User
#Region "Private Members"
Private m_FirstName As String
Private m_LastName As String
Private m_MI As String
Private m_Phone As String
Private m_Fax As String
Private m_Mobile As String
Private m_EmployeeID As String
Private m_ADsPath As String
Private m_UserName As String
Private m_DisplayName As String
Private m_OtherPhone As String
Private m_PhysicalDeliveryOfficeName As String
Private m_StreetAddress As String
Private m_City As String
Private m_State As String
Private m_PostalCode As String
Private m_Country As String
Private m_Email As String
Private m_BudgetCenter As String
#End Region
Now we need to expose these properties to calling classes:
#Region "Public Members"
Public Property FirstName() As String
Get
Return m_FirstName
End Get
Set(ByVal Value As String)
m_FirstName = Value
End Set
End Property
Public Property LastName() As String
Get
Return m_LastName
End Get
Set(ByVal Value As String)
m_LastName = Value
End Set
End Property
::
::
::
#End Region
End Class
Defining the Users Class
Next we'll create a class a Users class which will provide us with the abilty to enumerate through our users and create our collection of user objects:
Public Class Users
Implements IEnumerable
Private m_Users As New ArrayList
Public Function GetEnumerator() As _
System.Collections.IEnumerator Implements _
System.Collections.IEnumerable.GetEnumerator
Return New UserEnumerator(m_Users)
End Function
Public Sub New()
End Sub
Public ReadOnly Property Count() As Int32
Get
Return m_Users.Count
End Get
End Property
Public Sub AddUser(ByVal User As User)
m_Users.Add(User)
End Sub
Private Class UserEnumerator
Implements IEnumerator
Private m_Users As ArrayList
Private m_Position As Int32 = -1
Public Sub New(ByVal Users As ArrayList)
m_Users = Users
End Sub
Public ReadOnly Property Current() As _
Object Implements System.Collections.IEnumerator.Current
Get
Return m_Users(m_Position)
End Get
End Property
' Implementation of IEnumerator
Public Function MoveNext() As Boolean _
Implements System.Collections.IEnumerator.MoveNext
m_Position += 1
If m_Position >= m_Users.Count Then
Return False
Else
Return True
End If
End Function
' Implementation of IEnumerator
Public Sub Reset() Implements System.Collections.IEnumerator.Reset
m_Position = -1
End Sub
End Class
End Class
You can see from the previous class this extends the ArrayList and provides us with the ability to navigate through our Users collection.
The UserSearcher Class
In the next code block is what we use to populate the objects. Combined with the UserController class which provides the interaction with the backend system, in this case AD will create our User objects:
Public Class UserSearcher
Private _items As New Users
Public Sub New()
End Sub
Public Property SearchItems()
Get
Return _items
End Get
Set(ByVal Value)
_items = Value
End Set
End Property
Public Sub AddSearchItemResult(ByVal Item As User)
_items.AddUser(Item)
End Sub
End Class
UserController Class
Next we have our UserController class, which interacts with the backend systems, performs a search and then populates the users via the UserSearcher class. In order to instantiate our UserController, we will need to pass logon credentials for AD. This enables us to perform a search on AD and return the user collection:
Imports System.Security.Principal
Imports System.DirectoryServices
Imports System
Public Class UserController
#Region "Private Members"
'Private m_Users As New Users
Private m_Directory As DirectoryEntry
Private m_Domain As String
#End Region
#Region "Public Members"
'Object creation, pass the domain, username, and password for searching the AD
Public Sub New(ByVal Domain As String, ByVal UserName As String, _
ByVal Password As String)
'Domain = in the form of "LDAP://domain.com/DC=domain,DC=com"
m_Domain = Domain
m_Directory = New DirectoryEntry(m_Domain, UserName, Password)
End Sub
#End Region
#Region "Controller Methods"
'Accepts a filter string, and then searches AD for only users.
'Returns an array of Users typed as a Users class.
Public Function GetUsersByFilter(ByVal FilterString As _
String, Optional ByVal SizeLimit As Integer = 1000) As Users
Dim Searcher As New DirectorySearcher(m_Directory)
Dim SearchResultColl As SearchResultCollection
Dim objSearchResult As SearchResult
Searcher.SizeLimit = SizeLimit
'see if they're typing in an extension
Try
Dim extension As Integer = CType(FilterString.Replace("-", ""), Integer)
Searcher.Filter = "(&(objectClass=user)(telephoneNumber=*" & FilterString & "*))"
Catch ex As Exception
Searcher.Filter = "(&(objectClass=user)(displayName=" & FilterString & "*))"
End Try
SearchResultColl = Searcher.FindAll()
Dim oSearch As New UserSearcher
If Not SearchResultColl Is Nothing Then
For Each objSearchResult In SearchResultColl
Dim thisUser As New User
thisUser = BuildSingleUser(objSearchResult)
oSearch.AddSearchItemResult(thisUser)
Next
Return oSearch.SearchItems
Else
Return Nothing
End If
End Function
'This method handles the populatation of the User object.
Private Function BuildSingleUser(ByVal mySearchResult As SearchResult) As User
Dim myUser As New User
Dim myResultPropColl As ResultPropertyCollection
Dim myResultPropValueColl As ResultPropertyValueCollection
myResultPropColl = mySearchResult.Properties
myUser.Email = BuildField("mail", myResultPropColl)
myUser.DisplayName = BuildField("displayName", myResultPropColl)
myUser.FirstName = BuildField("givenName", myResultPropColl)
myUser.LastName = BuildField("sn", myResultPropColl)
myUser.StreetAddress = BuildField("streetAddress", myResultPropColl)
myUser.PostalCode = BuildField("postalCode", myResultPropColl)
myUser.City = BuildField("l", myResultPropColl)
myUser.State = BuildField("st", myResultPropColl)
myUser.Phone = BuildField("telephoneNumber", myResultPropColl)
myUser.Country = BuildField("co", myResultPropColl)
myUser.EmployeeID = BuildField("employeeid", myResultPropColl)
myUser.BudgetCenter = BuildField("extensionattribute6", myResultPropColl)
myUser.PhysicalDeliveryOfficeName = _
BuildField("PhysicalDeliveryOfficeName", myResultPropColl)
myUser.Fax = BuildField("facsimileTelephoneNumber", myResultPropColl)
myUser.Mobile = BuildField("mobile", myResultPropColl)
myUser.OtherPhone = BuildField("othertelephone", myResultPropColl)
myUser.ADsPath = BuildField("AdsPath", myResultPropColl)
Return myUser
End Function
'for trapping a null data type returned from AD.
Private Function BuildField(ByVal FieldName As String, _
ByVal myResultPropColl As ResultPropertyCollection) As String
If Not myResultPropColl Is Nothing Then
Try
Dim myResultPropValueColl As ResultPropertyValueCollection = _
myResultPropColl.Item(FieldName)
Return myResultPropValueColl.Item(0)
Catch ex As Exception
Return "Null"
End Try
Else
Return "Null"
End If
End Function
'function to pull a single user account based on their account name.
' you can the type returned is of type user.
Public Function GetSingleUserByAccount(ByVal AccountName As String) As User
Try
Dim Searcher As New DirectorySearcher(m_Directory)
Dim SearchResultColl As SearchResultCollection
Searcher.Filter = ("(&(objectClass=user)(samaccountname=" & AccountName & "))")
SearchResultColl = Searcher.FindAll()
If Not SearchResultColl Is Nothing Then
Return DefinedUser(SearchResultColl)
Else
Return Nothing
End If
Catch ex As Exception
Throw ex
End Try
End Function
Private Function DefinedUser(ByVal SearchResultColl As SearchResultCollection) As User
Dim myUser As New User
Dim mySearchResult As SearchResult
Dim myResultPropColl As ResultPropertyCollection
Dim myResultPropValueColl As ResultPropertyValueCollection
mySearchResult = SearchResultColl.Item(0)
myUser = BuildSingleUser(mySearchResult)
Return myUser
End Function
#End Region
End Class
Wrapping in Web Service
These classes are then compiled into their own assembly to be consumed by a client. The next block of code shows an example of a Web service calling our User assembly.
<WebMethod()> _
Public Function GetSingleUserByAccount(ByVal SAMAccountName As _
String, ByVal DomainRoot As String) As User
Dim myUser As New User
Dim mySearch As New UserController(DomainRoot, UserName, Password)
myUser = mySearch.GetSingleUserByAccount(SAMAccountName)
Return myUser
End Function
<WebMethod()> _
Public Function GetUsersByFilter(ByVal FilterString As _
String, ByVal DomainRoot As String) As User()
Dim myUsers As Users
Dim User As User
Dim al As New ArrayList
Dim mySearch As New UserController(DomainRoot, UserName, Password)
myUsers = mySearch.GetUsersByFilter(FilterString, 4000)
For Each User In myUsers
al.Add(User)
Next
Return al.ToArray(GetType(User))
End Function
You'll notice we maintain our types on the Web service calls but return an array of user objects back to the calling client of the Web service. You can also see that we do not expose the AD credentials in our service, nor do we allow the values to be passed over the Web or intranet in order to avoid any packet sniffers gaining access to the information. In this particular implementation we opted to use the DPAPI and store the values in the registry in an encrypted format.
Then just call your user object from your client and work directly with user objects.
In this example we just exposed AD, but you can consolidate information from various sources in your single user object for your applications.