Larry Steinle

March 6, 2011

AdConnection: Enforcing Active Directory Communication Best Practices

Filed under: Active Directory,VS.Net — Larry Steinle @ 10:08 am
Tags: , , ,

On the one hand, to avoid running out of memory requires disposing of your Active Directory objects as soon as you are done with them. On the other hand, if you dispose of all Active Directory objects you will run out of communication ports. In today’s post we will create an Active Directory Connection object aptly named, AdConnection, that will ensure shared connections are used while reducing the risk of running out of memory.

Active Directory Data Access Layer Series

This is the third post of an eight part series about the Active Directory Data Access Layer. As each post builds on the previous it may be helpful to review older posts prior to reading this one. If you would like to download a working copy of the AD DAL please refer to the download on the Code Share page.

AdConnection Responsibilities

To reduce the risk of memory leaks and unavailable communication ports it is recommended that a single class be responsible for the creation of DirectoryEntry and DirectorySearcher objects. The AdConnection object will ensure that connections are shared by using the connection string to create a DirectoryEntry object that serves one purpose: establish a connection with the domain. All future actions will use new instances of the DirectoryEntry object that can be safely disposed without affecting the shared connection.

The AdConnection class will track each instance of a DirectoryEntry and DirectorySearcher class. When the connection is closed AdConnection will verify that each object has been properly disposed prior to disposing the shared DirectoryEntry instance.

These Active Directory connection best practice actions all fit nicely within the context of the DbConnection interface. The open method clearly defines when to create the shared DirectoryEntry object. The close method clearly defines when to dispose of all instantiated objects and release memory resources.

AdConnection Class Diagram

AdConnection Class Diagram

The CreateDirectoryEntry and CreateDirectorySearcher objects will be marked Friend to hide their implementation from external libraries. Only the classes within our Active Directory Data Access Layer need use these methods. Of course you can change this to public if you believe it would provide additional benefits outside the system. However, in the spirit of reducing complexity while supporting the Db data model I have opted to hide them.

One last feature of the AdConnection is that it will be used to track connection errors. Since there may be multiple errors per connection we will need a list of error objects.

AdErrorCollection & AdException

These classes are very simple and intuitive to implement. The AdErrorCollection is simply a generic list of AdExceptions. The AdException class inherits from the DbException class. These classes will permit tracking multiple exceptions that may occur per call.

We will begin by creating the AdException class:

Namespace Data.ActiveDirectory
Public Class AdException
    Inherits System.Data.Common.DbException

    Public Sub New()
      MyBase.new()
    End Sub

    Public Sub New(ByVal message As String)
      MyBase.New(message)
    End Sub

    Public Sub New(ByVal message As String, ByVal errorCode As Integer)
      MyBase.New(message, errorCode)
    End Sub

    Public Sub New(ByVal message As String, ByVal innerException As System.Exception)
      MyBase.New(message, innerException)
      Errors.Add(innerException)
    End Sub

    Public Sub New(ByVal info As System.Runtime.Serialization.SerializationInfo, ByVal context As System.Runtime.Serialization.StreamingContext)
      MyBase.New(info, context)
    End Sub

    ''' <summary>
    ''' Set of errors associated with the exception.
    ''' </summary>
    ''' <remarks></remarks>
    Public ReadOnly Property Errors As AdErrorCollection
      Get
        If _ErrorCollection Is Nothing Then _ErrorCollection = New AdErrorCollection
        Return _ErrorCollection
      End Get
    End Property
    Private _ErrorCollection As AdErrorCollection

    ''' <summary>
    ''' Get a value that indicates how many records failed.
    ''' </summary>
    ''' <remarks></remarks>
    Public ReadOnly Property RecordsAffected As Integer
      Get
        Return _RecordsAffected
      End Get
    End Property
    Private _RecordsAffected As Integer = 0

    ''' <summary>
    ''' Increment the number of records affected by an error.
    ''' </summary>
    ''' <remarks></remarks>
    Friend Sub IncrementRecordsAffected()
      _RecordsAffected += 1
    End Sub
  End Class
End Namespace

Next the very simple AdErrorCollection class:

Namespace Data.ActiveDirectory
  ''' <summary>
  ''' Collection of errors associated thrown while executing an active directory command.
  ''' </summary>
  ''' <remarks></remarks>
  Public Class AdErrorCollection
    Inherits System.Collections.Generic.List(Of System.Exception)
  End Class
End Namespace

AdInfoEventArg

AdInfoEventArg will be used to send messages to the consumer of the connection class. Messages may be informational or may contain exception errors.

Namespace Data.ActiveDirectory
  Public Class AdInfoEventArg
    Inherits System.EventArgs

    Public Sub New()
      MyBase.New()
    End Sub

    Public Sub New(ByVal message As String)
      _Message = message
    End Sub

    Public Sub New(ByVal message As String, ByVal exception As System.Exception)
      _Message = message
      _Exception = exception
    End Sub

    Public ReadOnly Property Message As String
      Get
        Return _Message
      End Get
    End Property
    Private _Message As String

    Public ReadOnly Property Exception As System.Exception
      Get
        Return _Exception
      End Get
    End Property
    Private _Exception As System.Exception
  End Class
End Namespace

AdConnection

The AdConnection object will use the AdConnectionStringBuilder class to parse the connection string and properly connect to the domain. Additionally the Path object will be used to ensure that the path is valid and properly encoded.

Since DbConnection inherits from System.ComponentModel.Component you will need to select the view code option (F7) when opening the file. Simply double clicking on the class will open the designer window which isn’t supported. This is normal behavior.

Another responsibility that will be added to the AdConnection is to ensure that the User ID is valid for the specified connection. This responsibility is in the AdConnection class because the AdConnectionStringBuilder class is responsible for parsing the connection; not verifying that the connection is correct.

When connecting to Active Directory it is easiest to use a fully qualified sAMAccountName or userPrincipalName. Then no matter how the connection is established the account will work. However, if a partial sAMAccountName is used or a distinguished name (yes, a connection can even be established with a distinguished name) then there are specific rules that must be adhered to defining when they can and cannot be used.

Imports System.Configuration

Namespace Data.ActiveDirectory
  ''' <summary>
  ''' Represents an open connection to an Active Directory Domain Controller.
  ''' </summary>
  ''' <remarks></remarks>
Public NotInheritable Class AdConnection
    Inherits System.Data.Common.DbConnection

#Region "Definition Section"
    ''' <summary>
    ''' The directory entry to retain to ensure the connection will be shared.
    ''' </summary>
    ''' <remarks>
    ''' Connection pooling will occur when:
    ''' <list>
    ''' <item>The same server, port and credentials are used.</item>
    ''' <item>The same authentication flags are used.</item>
    ''' At least one DirEntry object stays open.
    ''' </list>
    ''' </remarks>
    Private _PoolEntry As System.DirectoryServices.DirectoryEntry

    ''' <summary>
    ''' Tracks the connections to the domain ensuring that resources
    ''' are disposed of correctly when completed.
    ''' </summary>
    ''' <remarks></remarks>
    Private _Connections As System.Collections.ArrayList

    ''' <summary>
    ''' Thrown when an information or warning occurs on the connection.
    ''' </summary>
    ''' <param name="sender">
    ''' The AdConnection object raising the message.
    ''' </param>
    ''' <param name="e">
    ''' The message to communicate.
    ''' </param>
    ''' <remarks></remarks>
    Event InfoMessage(ByVal sender As Object, ByVal e As AdInfoEventArg)
#End Region

#Region "Constructor Section"
    ''' <summary>
    ''' Instantiates a new instance of AdConnection.
    ''' </summary>
    ''' <remarks>
    ''' Attempts to retrieve connection string information for default domain controller.
    ''' </remarks>
    Public Sub New()
    End Sub

    ''' <summary>
    ''' Instantiates a new instance of AdConnection.
    ''' </summary>
    ''' <param name="connectionString">
    ''' The connection used to open the Active Directory database or the key
    ''' name referring to the connection string stored in AppSettings or Web.Config.
    ''' </param>
    ''' <remarks></remarks>
    Public Sub New(ByVal connectionString As String)
      Me.ConnectionString = connectionString
    End Sub
#End Region

#Region "Supporting Behaviors Section"
    ''' <summary>
    ''' Validates the type of user name supplied with the authentication type.
    ''' </summary>
    ''' <param name="userId">
    ''' The NT Account, User Principal Name or Distinguished Name to use to login to the domain.
    ''' </param>
    ''' <param name="<span class=" />authType">
    ''' Defines how to bind to the domain.
    ''' </param>
    ''' <returns>
    ''' True if the user name is valid for the specified authentication type, false otherwise.
    ''' </returns>
    ''' <remarks>
    ''' When connecting to ADAM use the full DN or the UPN.
    ''' Note the UPN does not require an @ sign in ADAM.
    ''' </remarks>
    Private Function IsUserNameValid(ByVal userId As String, ByVal authType As DirectoryServices.AuthenticationTypes) As Boolean
      If userId Is Nothing Then userId = String.Empty

      Dim isUPNFormat As Boolean = userId.Contains("@")
      Dim isQualifiedName As Boolean = userId.Contains("\")
      Dim isDN As Boolean = userId.ToUpper.Contains(",DC=")
      Dim isSecure As Boolean = ((authType And DirectoryServices.AuthenticationTypes.Secure) = DirectoryServices.AuthenticationTypes.Secure)

      If isUPNFormat OrElse isQualifiedName _
      OrElse (Not isDN AndAlso isSecure) _
      OrElse (isDN AndAlso Not isSecure) Then
        Return True
      Else
        Return False
      End If
    End Function

    ''' <summary>
    ''' Raises the LarrySteinle.Library.Data.ActiveDirectory.InfoMessage event.
    ''' </summary>
    ''' <param name="message">
    ''' The message to send to the listener.
    ''' </param>
    ''' <remarks></remarks>
    Friend Sub OnInfoMessage(ByVal message As String)
      OnInfoMessage(message, Nothing)
    End Sub

    ''' <summary>
    ''' Throw when the connection state changes.
    ''' </summary>
    ''' <param name="message">
    ''' The message to send to the listener.
    ''' </param>
    ''' <param name="exception">
    ''' The offending exception.
    ''' </param>
    ''' <remarks></remarks>
    Friend Sub OnInfoMessage(ByVal message As String, ByVal exception As System.Exception)
      RaiseEvent InfoMessage(Me, New AdInfoEventArg(message, exception))
    End Sub
#End Region

#Region "Resource Management"
    ''' <summary>
    ''' Gets or sets the value indicating the number of objects to read at a time.
    ''' </summary>
    ''' <remarks>
    ''' Leave this value at the default of 1000 unless your administrator has
    ''' changed the default page size on the domain controller.
    ''' </remarks>
    Public Property PageSize As Integer = 1000

    ''' <summary>
    ''' Creates a directory entry at the specified distinguished name path.
    ''' </summary>
    ''' <param name="path">
    ''' A distinguished name to the OU or DC path to connect.
    ''' </param>
    ''' <returns>
    ''' Returns an instantiated directory entry object.
    ''' </returns>
    ''' <exception cref="AdException">
    ''' Thrown when the system is unable to establish a connection to the domain controller.
    ''' </exception>
    ''' <remarks></remarks>
    Friend Function CreateDirectoryEntry(ByVal path As String) As System.DirectoryServices.DirectoryEntry
      Return CreateDirectoryEntry(path, True)
    End Function

    ''' <summary>
    ''' Creates a directory entry at the specified distinguished name path.
    ''' </summary>
    ''' <param name="path">
    ''' The path to the domain and ou to connect.
    ''' </param>
    ''' <param name="track">
    ''' When true causes the directory entry to be added to the pool
    ''' ensuring that its resources are released when the connection
    ''' is closed. When set to false the caller is responsible to
    ''' free the resource.
    ''' </param>
    ''' <returns>
    ''' Returns an instantiated directory entry object.
    ''' </returns>
    ''' <exception cref="AdException">
    ''' Thrown when the system is unable to establish a connection to the domain controller.
    ''' </exception>
    ''' <remarks>
    ''' Failure to correctly free resources may result in difficult to detect memory leaks.
    ''' </remarks>
    Friend Function CreateDirectoryEntry(ByVal path As String, ByVal track As Boolean) As System.DirectoryServices.DirectoryEntry
      'Connect to the directory entry.
      Dim dirEntry As System.DirectoryServices.DirectoryEntry = Nothing
      If IsUserNameValid(InnerBuilder.UserId, InnerBuilder.AuthenticationType) Then
        Dim adPath As New Path(path)

        'If a distinguishedName is passed into the routine then convert it to an
        'adsiPath supporting SearchRoot distinguishedNames for the Command object.
        If path.Trim.IndexOf("://") < 0 Then
          adPath.Provider = InnerBuilder.Provider
          adPath.HostName = InnerBuilder.DomainName
          adPath.PortNumber = InnerBuilder.PortNumber.ToString
          If path.Trim.StartsWith("/") Then
            adPath.DnValue = path.Trim.Substring(1)
          Else
            adPath.DnValue = path.Trim
          End If
        End If

        'Create the DirectoryEntry
        Try
          dirEntry = New System.DirectoryServices.DirectoryEntry(adPath.ToString, InnerBuilder.UserId, InnerBuilder.Password, InnerBuilder.AuthenticationType)
          If track Then TrackResource(dirEntry)
          OnStateChange(ConnectionState.Open)
        Catch ex As Exception
          If dirEntry IsNot Nothing Then
            If track Then
              FreeResource(dirEntry)
            Else
              dirEntry.Dispose()
              dirEntry = Nothing
            End If
          End If
          OnStateChange(ConnectionState.Broken)
          Throw New AdException("An unexpected error occurred while opening a connection to the domain controller.", ex)
        End Try
      Else
        OnStateChange(ConnectionState.Broken)
        Throw New AdException("Invalid user id provided for selected authentication type.")
      End If

      Return dirEntry
    End Function

    ''' <summary>
    ''' Get an instance of the DirectorySearcher object.
    ''' </summary>
    ''' <param name="<span class=" />searchFilter">
    ''' The adsi filter to use to search for the objects.
    ''' </param>
    ''' <returns>
    ''' Returns an instance of the DirectorySearcher object.
    ''' </returns>
    ''' <remarks></remarks>
    Friend Function CreateDirectorySearcher(ByVal searchFilter As String) As System.DirectoryServices.DirectorySearcher
      Dim connectionBuilder As New AdConnectionStringBuilder(ConnectionString)
      Return CreateDirectorySearcher(searchFilter, connectionBuilder.RootPath, System.DirectoryServices.SearchScope.Subtree)
    End Function

    ''' <summary>
    ''' Get an instance of the DirectorySearcher object.
    ''' </summary>
    ''' <param name="<span class=" />searchFilter">
    ''' The adsi filter to use to search for the objects.
    ''' </param>
    ''' <param name="<span class=" />searchRoot">
    ''' Specifies the OU to contain the search scope.
    ''' </param>
    ''' <returns>
    ''' Returns an instance of the DirectorySearcher object.
    ''' </returns>
    ''' <remarks></remarks>
    Friend Function CreateDirectorySearcher(ByVal searchFilter As String, ByVal searchRoot As String) As System.DirectoryServices.DirectorySearcher
      Return CreateDirectorySearcher(searchFilter, searchRoot, System.DirectoryServices.SearchScope.Subtree)
    End Function

    ''' <summary>
    ''' Get an instance of the DirectorySearcher object.
    ''' </summary>
    ''' <param name="searchFilter">
    ''' The adsi filter to use to search for the objects.
    ''' </param>
    ''' <param name="searchRoot">
    ''' Specifies the OU to contain the search scope.
    ''' </param>
    ''' <param name="scope">
    ''' Specifies the scope of the search.
    ''' </param>
    ''' <returns>
    ''' Returns an instance of the DirectorySearcher object.
    ''' </returns>
    ''' <remarks></remarks>
    Friend Function CreateDirectorySearcher(ByVal searchFilter As String, ByVal searchRoot As String, ByVal scope As System.DirectoryServices.SearchScope) As System.DirectoryServices.DirectorySearcher
      Dim workEntry As System.DirectoryServices.DirectoryEntry = CreateDirectoryEntry(searchRoot)
      Dim dirSearcher As System.DirectoryServices.DirectorySearcher = New System.DirectoryServices.DirectorySearcher(workEntry, searchFilter)

      dirSearcher.ClientTimeout = TimeSpan.FromSeconds(ConnectionTimeout)
      dirSearcher.PageSize = PageSize
      dirSearcher.ReferralChasing = DirectoryServices.ReferralChasingOption.All
      dirSearcher.SearchScope = scope

      TrackResource(dirSearcher)
      Return dirSearcher
    End Function

    ''' <summary>
    ''' Adds a resource to the pool.
    ''' </summary>
    ''' <param name="resource">
    ''' The resource to add to the pool.
    ''' </param>
    ''' <remarks></remarks>
    Private Sub TrackResource(ByVal resource As Object)
      If TypeOf resource Is System.DirectoryServices.DirectoryEntry _
      OrElse TypeOf resource Is System.DirectoryServices.DirectorySearcher Then
        If _Connections Is Nothing Then _Connections = New System.Collections.ArrayList
        _Connections.Add(resource)
      Else
        Throw New ArgumentException("Invalid resource type provided. Resource must be of type DirectoryEntry or DirectorySearcher.")
      End If
    End Sub

    ''' <summary>
    ''' Releases the resource removing it from the internal tracking system.
    ''' </summary>
    ''' <param name="resource">
    ''' The Active Directory resource to release.
    ''' </param>
    ''' <remarks></remarks>
    Friend Sub FreeResource(ByVal resource As System.DirectoryServices.DirectoryEntry)
      resource.Dispose()
      _Connections.Remove(resource)
      resource = Nothing
    End Sub

    ''' <summary>
    ''' Releases the resource removing it from the internal tracking system.
    ''' </summary>
    ''' <param name="resource">
    ''' The Active Directory resource to release.
    ''' </param>
    ''' <remarks></remarks>
    Friend Sub FreeResource(ByVal resource As System.DirectoryServices.DirectorySearcher)
      If resource.SearchRoot IsNot Nothing Then FreeResource(resource.SearchRoot)
      resource.Dispose()
      _Connections.Remove(resource)
      resource = Nothing
    End Sub

    ''' <summary>
    ''' Releases com resources to all active connections.
    ''' </summary>
    ''' <remarks></remarks>
    Friend Sub Flush()
      If _Connections IsNot Nothing Then
        While _Connections.Count > 0
          Dim resource As Object = _Connections(_Connections.Count - 1)

          If resource Is Nothing Then
            'Do Nothing
          ElseIf TypeOf resource Is System.DirectoryServices.DirectoryEntry Then
            FreeResource(DirectCast(resource, System.DirectoryServices.DirectoryEntry))
          ElseIf TypeOf resource Is System.DirectoryServices.DirectorySearcher Then
            FreeResource(DirectCast(resource, System.DirectoryServices.DirectorySearcher))
          Else
            If TypeOf resource Is IDisposable Then
              DirectCast(resource, IDisposable).Dispose()
            End If

            _Connections.Remove(resource)
          End If
        End While

        _Connections = Nothing
      End If
    End Sub
#End Region

#Region "Implements IDbConnection Interface"
    ''' <summary>
    ''' Raises the System.Data.Common.DbConnection.StateChanged event.
    ''' </summary>
    ''' <param name="state">
    ''' The current state of the system.
    ''' </param>
    ''' <remarks></remarks>
    Friend Shadows Sub OnStateChange(ByVal state As ConnectionState)
      If _State <> state Then
        MyBase.OnStateChange(New StateChangeEventArgs(_State, state))
        _State = state
      End If
    End Sub

    ''' <summary>
    ''' Starts a database transaction.
    ''' </summary>
    ''' <param name="isolationLevel">
    ''' Specifies the isolation level for the transaction.
    ''' </param>
    ''' <returns>
    ''' An object representing the new transaction.
    ''' </returns>
    ''' <remarks></remarks>
    Protected Overrides Function BeginDbTransaction(ByVal isolationLevel As System.Data.IsolationLevel) As System.Data.Common.DbTransaction
      Return Nothing
    End Function

    ''' <summary>
    ''' Changes the current database for an open connection.
    ''' </summary>
    ''' <param name="databaseName">
    ''' Specifies the name of the database for the connection to use.
    ''' </param>
    ''' <remarks></remarks>
    Public Overrides Sub ChangeDatabase(ByVal databaseName As String)
      Dim isOpen As Boolean = (State = ConnectionState.Open)
      If isOpen Then Close()
      Dim dbcon As New System.Data.SqlClient.SqlConnection

      Dim connectionBuilder As New AdConnectionStringBuilder(ConnectionString)
      connectionBuilder.DomainController = databaseName
      ConnectionString = connectionBuilder.ConnectionString

      If isOpen Then Open()
    End Sub

    ''' <summary>
    ''' Closes the connection to the database. This is the preferred method of closing any open connection.
    ''' </summary>
    ''' <remarks></remarks>
    Public Overrides Sub Close()
      If _PoolEntry IsNot Nothing Then
        Flush()
        _PoolEntry.Dispose()
        _PoolEntry = Nothing
        OnStateChange(ConnectionState.Closed)
      End If
    End Sub

    Friend ReadOnly Property InnerBuilder As AdConnectionStringBuilder
      Get
        If _InnerBuilder Is Nothing Then
          _InnerBuilder = New AdConnectionStringBuilder
        End If
        Return _InnerBuilder
      End Get
    End Property
    Private _InnerBuilder As AdConnectionStringBuilder

    ''' <summary>
    ''' Gets or sets the string used to open the connection.
    ''' </summary>
    ''' <remarks></remarks>
    Public Overrides Property ConnectionString As String
      Get
        Return InnerBuilder.ConnectionString
      End Get
      Set(ByVal value As String)
        Dim conString As String
        Dim conSettings As ConnectionStringSettings = ConfigurationManager.ConnectionStrings.Item(value)
        Dim appSetting As String = ConfigurationManager.AppSettings.Item(value)

        If conSettings IsNot Nothing _
        AndAlso Not String.IsNullOrWhiteSpace(conSettings.ConnectionString) Then
          conString = conSettings.ConnectionString
        ElseIf Not String.IsNullOrWhiteSpace(appSetting) Then
          conString = appSetting
        Else
          conString = value
        End If

        _InnerBuilder = New AdConnectionStringBuilder(conString)
      End Set
    End Property

    ''' <summary>
    ''' Creates and returns a DbCommand object associated with the current connection.
    ''' </summary>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Protected Overrides Function CreateDbCommand() As System.Data.Common.DbCommand
      Return DirectCast(New AdCommand(String.Empty, Me), System.Data.Common.DbCommand)
    End Function

    ''' <summary>
    ''' Creates and returns an AdCommand object associated with the current connection.
    ''' </summary>
    ''' <returns>
    ''' An instance of an AdCommand object.
    ''' </returns>
    ''' <remarks></remarks>
    Public Overloads Function CreateCommand() As AdCommand
      Return New AdCommand(String.Empty, Me)
    End Function

    ''' <summary>
    ''' Gets the name of the current database after a connection is opened,
    ''' or the database name specified in the connection string before the
    ''' connection is opened.
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Overrides ReadOnly Property Database As String
      Get
        Return InnerBuilder.DomainController
      End Get
    End Property

    ''' <summary>
    ''' Gets the name of the database server to which to connect.
    ''' </summary>
    ''' <remarks></remarks>
    Public Overrides ReadOnly Property DataSource As String
      Get
        Return InnerBuilder.DomainName
      End Get
    End Property

    ''' <summary>
    ''' Opens a database connection with the settings specified by the ConnectionString.
    ''' </summary>
    ''' <remarks></remarks>
    Public Overrides Sub Open()
      If State = ConnectionState.Closed Then
        Try
          OnStateChange(ConnectionState.Connecting)
          _PoolEntry = CreateDirectoryEntry(InnerBuilder.RootPath, False)
          _PoolEntry.RefreshCache()
        Catch ex As Exception
          If _PoolEntry IsNot Nothing Then Close()
          OnStateChange(ConnectionState.Broken)
          Throw ex
        End Try
      End If
    End Sub

    ''' <summary>
    ''' Gets a string that represents the version of the server to which the object is connected.
    ''' </summary>
    ''' <remarks>Not supported in our implementation.</remarks>
    Public Overrides ReadOnly Property ServerVersion As String
      Get
        Return String.Empty
      End Get
    End Property

    ''' <summary>
    ''' Gets a string that describes the state of the connection.
    ''' </summary>
    ''' <remarks></remarks>
    Public Overrides ReadOnly Property State As System.Data.ConnectionState
      Get
        If _State <> ConnectionState.Closed AndAlso _PoolEntry Is Nothing Then
          Return ConnectionState.Broken
        End If
        Return _State
      End Get
    End Property
    Private _State As System.Data.ConnectionState = ConnectionState.Closed

    ''' <summary>
    ''' Releases all resources used by the Component and optionally releases the managed resources.
    ''' </summary>
    ''' <param name="disposing">
    ''' True to release both managed and unmanaged resources; false to release only unmanaged resources.
    ''' </param>
    ''' <remarks></remarks>
    Protected Overrides Sub Dispose(ByVal disposing As Boolean)
      Close()
      MyBase.Dispose(disposing)
    End Sub
#End Region
  End Class
End Namespace

Summary

Today we learned how to manage active directory objects and connections from a single class to ensure that our connections are shared and resources are properly disposed. This post documented the AdConnection object as part of the Active Directory Data Access Layer series.

Advertisement

Leave a Comment »

No comments yet.

RSS feed for comments on this post. TrackBack URI

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

Create a free website or blog at WordPress.com.

%d bloggers like this: