Accessing Global Address List via System.DirectoryServices Namespace

The System.DirectoryServices namespace provides easy access to Active Directory from managed code. The namespace contains two component classes,  DirectoryEntry and  DirectorySearcher, which use the Active Directory Services Interfaces (ADSI) technology. ADSI is the set of interfaces that Microsoft provides as a flexible tool for working with a variety of network providers.

This is a how-to article to access the GAL from ASP.NET, using the DropDownList ASP.NET server control as the UI. To accomplish this the article will illustrate by utilizing ADSI technology from a C# assembly returning an ArralList in order to bind to the DropDownList.

1) Open Microsoft Visual Studio .NET.

2) Click New Project. Select the Visual C# Projects Item in the Project Types panel on the left. Select Class Library in the Templates panel on the right. Name the project 'gal' or any name you choose in the Name text box and state the location of the project in the Location text box. And click OK to complete the project description.

 

gal1.gif
3) In the Solution explorer right-click the References item and click Add Reference in the pop-up menu. The Add Reference dialog will be displayed. Select the .NET tab and highlight the System.DirectoryServices.dll, then click the Select button on the right to accept and click OK to add the reference to the Solution.

gal2.gif

You will now see the added reference to the project.

gal3.gif

4) Add to the using declaration section at the top just below the 'using System;' declaration the following two lines.

  1. using System.DirectoryServices;  
  2. using System.Collections; 

gal4.gif

These using declarations are needed in order to access the classes of both namespaces, the DirectoryServices namespace to create the DirectorySearcher and DirectoryEntry objects, and the Collections namespace to create the ArrayList object.

You may want to click the link below as a reference to view the methods and properties in this namespace while building the solution.

https://docs.microsoft.com/en-us/dotnet/api/system.directoryservices?view=netframework-4.7.2

Note the Default Global Address List Properties from the Exchange Server below. Its default Filter rules are shown.

gal5.gif

gal6.gif

Note the 'Users' property in the Canonical name of object definition below. We will use this property in our filter query.

(objectClass=user)

gal7.gif

In this example I will shorten the filter rules to the following for simplicity sake:

(& (mailnickname=*)(objectClass=user))

shown below in the code.

The (mailnickname=*) filter applies the filter to every object that has a mailNickname attribute (that is, every object that is mail-enabled). The '&' is the equivalent to 'And' in T-Sql syntax. Exchange filter rules' order are not as intuitive as T-Sql as one can see. So the filter in the code means every object that is mail-enabled and every object that is defined as user.

5) Copy and Paste the following code in the class below the default constructor.

  1. public ArrayList returngal()  
  2. {  
  3.     DirectorySearcher objsearch = new DirectorySearcher();  
  4.     string strrootdse = objsearch.SearchRoot.Path;  
  5.     DirectoryEntry objdirentry = new DirectoryEntry(strrootdse);  
  6.     objsearch.Filter = "(& (mailnickname=*)(objectClass=user))";  
  7.     objsearch.SearchScope = System.DirectoryServices.SearchScope.Subtree;  
  8.     objsearch.PropertiesToLoad.Add("cn");  
  9.     objsearch.PropertyNamesOnly = true;  
  10.     objsearch.Sort.Direction = System.DirectoryServices.SortDirection.Ascending;  
  11.     objsearch.Sort.PropertyName = "cn";  
  12.     SearchResultCollection colresults = objsearch.FindAll();  
  13.     ArrayList arrGal = new ArrayList();  
  14.     foreach (SearchResult objresult in colresults)  
  15.     {  
  16.         arrGal.Add(objresult.GetDirectoryEntry().Properties["cn"].Value);  
  17.     }  
  18.     objsearch.Dispose();  
  19.     return arrGal;  
  20. } 

gal8.gif

6) Click on the 'Build' menu item, then click on 'Build Solution' in the pop-up menu.

gal9.gif

You will see the following successful build message in the Output/Build pane.

gal10.gif

7) Click on the 'File' menu item, then click on 'Save gal.sln' in the pop-up menu to save the project solution.

gal11.gif

Now we have a .NET assembly ready to use in our web page.

8) On the chosen web server create a new folder, name it 'gal', under the wwwroot directory.

gal12.gif

9) Create a virtual directory on the chosen web server using the Virtual Directory Creation Wizard to input the Alias, name it 'gal', the Directory, choose the newly created folder 'gal', and the Access Permissions(check the Read and Run Scripts at least) properties.

gal13.gif

gal14.gif

10) Open upon notepad and copy and paste the following code into the editor. Now save the file as 'gal.aspx' into the gal directory on the web server.

  1. <%@ Page Language="c#" Debug="true" %>  
  2. <html>  
  3. <script language="c#" runat="server">  
  4. void Page_Load(object sender, EventArgs e)  
  5. {   
  6. gal.Class1 s = new gal.Class1();   
  7. gal.DataSource = s.returngal();  
  8. gal.DataBind();  
  9. gal.Items.Insert( 0, new ListItem("GAL") );   
  10. }  
  11. </script>  
  12. <body>  
  13. GAL  
  14. <form id="Form" runat="server">  
  15. <br>  
  16. <ASP:DropDownList id="gal" runat="server" />  
  17. </form>  
  18. </body>  
  19. </html> 

11) Copy and paste the following code into the editor. Now save the file as 'web.config' into the gal directory on the web server.

  1. <?xml version="1.0" encoding="utf-8" ?>  
  2. <configuration>   
  3. <system.web>   
  4. <customErrors mode="Off" />   
  5. <authentication mode="None" />   
  6. </system.web>  
  7. </configuration> 

12) Create a new folder, name it 'bin', under the gal directory of the web server. Now copy the 'gal.dll' from the Debug folder which is under the bin folder in the project folder 'gal' and paste this gal.dll into the 'bin' folder of the web server.

Copy from the folder below.

gal15.gif

Paste it into the folder below.

gal16.gif

13) Type the path to the gal.aspx in the browser and see results below.

gal17.gif

If error messages appear, there may be problems with ASP.NET Base Account

See the following article at
 
https://support.microsoft.com/en-us/help/329986/how-to-use-the-system-directoryservices-namespace-in-asp-net

The web.config file may need some changes and the security of the virtual directory may need some adjusting. The article above should lend some light on the issue.

A possible solution could be the following:

Edit the web.config file.
  1. <authentication mode="Windows" />   
  2. <identity impersonate="true" /> 
And change the virtual directory security setting. Clear the Anonymous Authentication check box and check the Integrated Windows Authentication property.

 


There may be an issue with the performance of the page on request due to the size of the GAL. You may have to implement some caching into your gal.aspx page similar to the following.

  1. ArrayList arrGal = new ArrayList();   
  2. arrGal = (ArrayList)Cache["arrGal"];   
  3. if ( arrGal == null)   
  4. {  
  5.     gal.Class1 s = new gal.Class1();  
  6.     arrGal = s.returngal();   
  7.     Cache["arrGal"] = arrGal ;  
  8. }  
  9. gal.DataSource = arrGal ;  
  10. gal.DataBind();  
  11. gal.Items.Insert( 0, new ListItem("GAL") );  
This will greatly affect the page's performance and enhance the user's experience.

You may want to use a cache dependency like the following:

Cache.Insert("arrGal ", arrGal, null, DateTime.Now.AddMinutes(2), NoSlidingExpiration);

There may be an issue with the number of objects returned from the objsearch.FindAll() method. By default it will only return a maximum of 1000 objects in the SearchResultCollection. So you might have to do a little coding here. A possible solution is to have more than one implementation of FindAll() in the code, using a property to limit the searches eventually grabbing all the objects. I will use the 'cn' property to illustrate. The cn property is short for the common name attribute.

objsearch.Filter = "(& (mailnickname=*)(objectClass=user) (cn<=M))";

for one search and

objsearch.Filter = "(& (mailnickname=*)(objectClass=user) (cn>=M))";

for the second search, effectively using two, or more if needed, searches to select all the objects resulting in two or more SearchResultCollection objects to loop through.

  1. foreach(SearchResult objresult in colresults)  
  2. {  
  3.     arrGal.Add(objresult.GetDirectoryEntry().Properties["cn"].Value);  
  4. }   
  5. foreach(SearchResult objresult2 in colresults2)  
  6. {  
  7.     arrGal.Add(objresult2.GetDirectoryEntry().Properties["cn"].Value);  
  8. }