Accessing the Active Directory From Microsoft .NET

Introduction:

Some days ago, I saw in the CSharpCorner community's forum one question about how to access the Active Directory (AD) from Microsoft.NET. So, this article is intended to explain the architectural design of an application querying the AD for information.

Microsoft Active Directory is a directory service that provides the foundation for distributed networks built on Windows. The Active Directory APIs provide access to the data stored in this directory. It is a programming model very easy to understand and use.

Active Directory Architecture:

The direct ory System Agent (DSA) is the process that provides access to the store. The store is the physical store of directory information located on a hard disk. Clients access the directory using one of the following mechanisms supported by the DSA:

  • LDAP clients connect to the DSA using the LDAP protocol. LDAP is an acronym for Lightweight Directory Access Protocol. Active Directory supports LDAP 3.0, defined by RFC 2251, and LDAP 2.0, defined by RFC 1777. 

  • MAPI clients such as Microsoft Exchange connect to the DSA using the MAPI remote procedure call interface. 

  • Windows clients that use a previous version of Windows NT connect to the DSA using the Security Account Manager (SAM) interface. 

  • Active Directory DSA's connect to each other to perform replication using a proprietary remote procedure call interface.

The Active Directory data model is derived from the X.500 data model. The directory holds objects that represent things of various sorts, described by attributes. The universe of objects that can be stored in the directory is defined in the schema. For each object class, the schema defines what attributes an instance of the class must have, what additional attributes it may have, and what object class can be a parent of the current object class.

The Active Directory schema is implemented as a set of object class instances stored in the directory. This is very different than many directories that have a schema but store it as a text file read at startup. Storing the schema in the directory has many advantages. For example, user applications can read it to discover what objects and properties are available.

Active Directory can consist of many partitions or naming contexts. The Distinguished Name (DN) of an object includes enough information to locate a replica of the partition that holds the object. The user or application however often does not know the DN of the target object or which partition might contain the object. The Global Catalog (GC) allows users and applications to find objects in an Active Directory domain tree, given one or more attributes of the target object. The Global Catalog contains a partial replica of every naming context in the directory. It contains the schema and configuration naming contexts as well. This means the GC holds a replica of every object in the Active Directory but with only a small number of their attributes.

The Global Catalog is built automatically by the Active Directory replication system. The replication topology for the Global Catalog is generated automatically. The properties replicated into the Global Catalog include a base set defined by Microsoft. Administrators can specify additional properties to meet the needs of their installation.

Interfaces for accessing the Active Directory:

  1. LDPA: The Lightweight Directory Access Protocol (LDAP) is a directory service protocol that runs on a layer above the TCP/IP stack, and provides a mechanism for connecting to, searching, and modifying internet directories. The LDAP directory service is based on a client-server model. The function of LDAP is to allow access to an existing directory. The data model (data and namespace) of LDAP is similar to that of the X.500 OSI directory service, but with lower resource requirements due to its streamlined features. The associated LDAP API simplifies writing internet directory service applications.

  2. ADSI: Active Directory Service Interfaces (ADSI) is a set of COM interfaces used to access the capabilities of directory services from various network providers in a distributed computing environment, to present a single set of directory service interfaces for managing network resources. Administrators and developers can use ADSI services to enumerate and manage the resources in a directory service, regardless of the network environment that contains the resource.

  3. System.DirectoryServices: System.DirectoryServices is a namespace in the .NET Framework that provides simple programming access to LDAP directories such as Active Directory. System.DirectoryServices is built on the Active Directory Service Interfaces (ADSI) API.

Using System.DirectoryServices namespace:

This article will emphasize the benefits of using the namespace System.DirectoryServices, such as:

  • Designed completely within common language runtime parameters. System.DirectoryServices leverages common language runtime features, such as garbage collection, custom indexer, and dictionaries (hashtables). It also offers other common language runtime features such as automatic memory management, efficient deployment, an object-oriented framework, evidence-based security and exception handling. 

  • Simple to use. Although ADSI scripting was effective for many tasks, C++ applications for ADSI are sometimes difficult to develop. System.DirectoryServices implements some basic ADSI tasks to enable more efficient and effective application development.

System administrators can use System.DirectoryServices to automate tasks to manage network resources in the directory, such as users and computers and also to build applications that search, create, or modify objects in a directory.

You can develop many business objects for accessing the Active Directory, leveraging any application that needs the platform as its main database and for publishing objects in an enterprise network.

Listing 1 illustrates an example of the definition of a business object whose behavior is interacting with the AD and that changes the password for a specific user. The contract is specified in the IADPasswdManager interface and the implementation resides in the ADPasswdManager class.

As you can see in the code the AD is Transaction Processing System, the operation runs atomically as a logical unit of work, and if everything is OK, you commit the changes otherwise roll back to the previous consistent state. See the method CommitChanges highlighted in silver.

Listing 1

using System;

using System.DirectoryServices;

 

namespace OLAActiveDirectory.Management

{

    public interface IADPasswdManager

    {

        void ChangePassword(IADUser objUser, string strOldPasswd, string strNewPasswd);

        void SetPassword(IADUser objUser, string strPasswd);

    }

    public class ADPasswdManager : IADPasswdManager

    {

        public ADPasswdManager()

        {

        }

        public void SetPassword(IADUser objUser, string strPasswd)

        {

            DirectoryEntry objLoginEntry = objUser.DirectoryEntry;

            if (objLoginEntry != null)

            {

                objLoginEntry.Invoke("SetPassword", new object[] { strPasswd });

                objLoginEntry.CommitChanges();

            }

        }

        public void ChangePassword(IADUser objUser, string strOldPasswd, string strNewPasswd)

        {

            DirectoryEntry objLoginEntry = objUser.DirectoryEntry;

            if (objLoginEntry != null)

            {

                objLoginEntry.Invoke("ChangePassword", new object[] { strOldPasswd, strNewPasswd });

                objLoginEntry.CommitChanges();

            }

        }

    }
}

Then, a business entity must be defined to represent the users in the directory. It holds the information of a specific user in the directory knowing its Distinguished Name (DN). It defines an interface IADUser and the implementation is realized in the class ADUser as shown in the Listing 2.

Listing 2

using System;

using System.DirectoryServices;

using System.Collections;

 

namespace OLAActiveDirectory.Management

{

    public interface IADUser

    {

        DirectoryEntry DirectoryEntry { get;}

        bool IsUser { get;}

        PropertyValueCollection this[string strKey] { get;}

    }

 

    public class ADUser : IADUser

    {

        private readonly DirectoryEntry m_objUserEntry;

 

        public ADUser(string strLogin, string strRootPath)

        {

            DirectoryEntry objRootEntry = new DirectoryEntry(strRootPath);

            DirectorySearcher objADSearcher = new DirectorySearcher(objRootEntry);

 

            objADSearcher.Filter = "(&(objectClass=user)(anr=" + strLogin + "))";

            SearchResult objResult = objADSearcher.FindOne();

 

            this.m_objUserEntry = (objResult != null) ? objResult.GetDirectoryEntry() : null;

        }

        public DirectoryEntry DirectoryEntry

        {

            get

            {

                return this.m_objUserEntry;

            }

        }

        public PropertyValueCollection this[string strKey]

        {

            get

            {

                return this.m_objUserEntry.Properties[strKey];

            }

        }

        public bool IsUser

        {

            get

            {

                return this.m_objUserEntry != null;

            }

        }

    }
}

And finally, we define the helper class ADUserInfoShower and its underlying interface IADUserInfoShower whose role is to create an information label for a specific queried user. This object can be instantiated in the presentation layer and is independent of the technology used for showing the user information as illustrated in Listing 3. That is, this label can be rendered in a Web Browser, a Windows Client and a Mobile Device.


Listing 3

using System;

 

namespace OLAActiveDirectory.Management

{

    public interface IADUserInfoShower

    {

        string GetInformation(IADUser objUser, string strSep);

    }

 

    public class ADUserInfoShower : IADUserInfoShower

    {

        private string prvInfoBuilder(IADUser objUser, string strSep)

        {

            string strResult;

 

            strResult = "Fullname:" + objUser["givenName"].Value + " " + objUser["sn"].Value;

            strResult += strSep + "Mail:" + objUser["mail"].Value;

            strResult += strSep + "Telephone(s):" + objUser["telephoneNumber"].Value;

            foreach (string strPhone in objUser["otherTelephone"])

                strResult += strSep + strPhone;

 

            return strResult;

        }

        public ADUserInfoShower()

        {

        }

        public string GetInformation(IADUser objUser, string strSep)

        {

            return this.prvInfoBuilder(objUser, strSep);

        }

    }

}

Conclusion:

This article illustrates how you can interact with the Active Directory for querying information using Microsoft.NET technologies.

Next Recommended Readings