Common System.DirectoryServices Patternshttp://forums.asp.net/t/907421.aspx/1?Common+System+DirectoryServices+PatternsTue, 02 Aug 2005 15:46:26 -04009074211007517http://forums.asp.net/p/907421/1007517.aspx/1?Common+System+DirectoryServices+PatternsCommon System.DirectoryServices Patterns <font size="5">Common Patterns in System.DirectoryServices</font><br> <br> <font color="#0000ff" size="4"><i><b>Searching the Directory:</b></i></font><br> <br> <ol> <li>Create a DirectoryEntry that represents your SearchRoot.&nbsp; Your searches will be rooted to this location and will have the same permissions as the bound SearchRoot.&nbsp; Failure to specify a SearchRoot (bad practice) means you will attempt to search the entire current domain (as specified by RootDSE defaultNamingContext) and can be problematic for ASP.NET applications. </li><li>Create your LDAP query. </li><li>Optionally specify PropertiesToLoad collection - this will be more efficient if specified.&nbsp; However, it will return all available, non-constructed attributes if left null (Nothing in VB.NET). </li><li>For v1.1 S.DS, use the DirectorySearcher.FindAll() for all searches.&nbsp; The DirectorySearcher.FindOne() has a memory leak in certain situations - .NET 2.0 is unaffected and safe to use.<br> </li></ol> <i><b>Sample For Retrieving Only 1 Result:</b></i><br> <br> <font face="Courier New" size="2">DirectoryEntry searchRoot = new DirectoryEntry(<br> &nbsp;&nbsp;&nbsp; &quot;LDAP://server/OU=People,DC=domain,DC=com&quot;, //searches will be rooted under this OU<br> &nbsp;&nbsp;&nbsp; &quot;domain\\user&quot;, //we will use these credentials<br> &nbsp;&nbsp;&nbsp; &quot;password&quot;,<br> &nbsp;&nbsp;&nbsp; AuthenticationTypes.Secure<br> &nbsp;&nbsp;&nbsp; );<br> <br> using (searchRoot) //we are responsible to Dispose this!<br> {<br> &nbsp;&nbsp;&nbsp; DirectorySearcher ds = new DirectorySearcher(<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; searchRoot,<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &quot;(sn=Smith)&quot;, //here is our query<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; new string[] {&quot;sn&quot;} //optionally specify attributes to load<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; );<br> <br> &nbsp;&nbsp;&nbsp; ds.SizeLimit = 1;<br> <br> &nbsp;&nbsp;&nbsp; SearchResult sr = null;<br> &nbsp;&nbsp;&nbsp; <br> &nbsp;&nbsp;&nbsp; using (SearchResultCollection src = ds.FindAll())<br> &nbsp;&nbsp;&nbsp; {<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (src.Count &gt; 0)<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; sr = src[0];<br> &nbsp;&nbsp;&nbsp; }<br> <br> &nbsp;&nbsp;&nbsp; if (sr != null)<br> &nbsp;&nbsp;&nbsp; {<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //now use your SearchResult<br> &nbsp;&nbsp;&nbsp; }<br> }</font><br> <br> <i><b>Sample For Retrieving Multiple Results:</b></i><br> <br> <font face="Courier New" size="2">DirectoryEntry searchRoot = new DirectoryEntry(<br> &nbsp;&nbsp;&nbsp; &quot;LDAP://server/OU=People,DC=domain,DC=com&quot;, //searches will be rooted under this OU<br> &nbsp;&nbsp;&nbsp; &quot;domain\\user&quot;, //we will use these credentials<br> &nbsp;&nbsp;&nbsp; &quot;password&quot;,<br> &nbsp;&nbsp;&nbsp; AuthenticationTypes.Secure<br> &nbsp;&nbsp;&nbsp; );<br> <br> using (searchRoot) //we are responsible to Dispose this!<br> {<br> &nbsp;&nbsp;&nbsp; DirectorySearcher ds = new DirectorySearcher(<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; searchRoot,<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &quot;(sn=Smith)&quot;, //here is our query<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; new string[] {&quot;sn&quot;} //optionally specify attributes to load<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; );<br> <br> &nbsp;&nbsp;&nbsp; ds.PageSize = 1000; //enable paging for large queries<br> <br> &nbsp;&nbsp;&nbsp; using (SearchResultCollection src = ds.FindAll())<br> &nbsp;&nbsp;&nbsp; {<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; foreach (SearchResult sr in src)<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //use the SearchResult here<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br> &nbsp;&nbsp;&nbsp; }<br> }</font><br> <br> Notice the common pattern here and how all DirectoryEntry and SearchResultCollection classes are Disposed.&nbsp; Failure to do so can leak memory.<br> <br> <i><b><font color="#0000ff" size="4">Reading Attributes:</font></b></i><br> <br> One of the most common issues that people run into is getting an error trying to read the attribute when it does not exist.&nbsp; It is important to understand there is no concept of null attributes in Active Directory - the attribute either exists on the object or it doesn't - it is never null.&nbsp; This however can be confusing because trying to read a non-existant attribute culminates in a null reference exception in .NET.<br> <br> To protect against this, use this pattern:<br> <br> <font face="Courier New" size="2">DirectoryEntry entry = new DirectoryEntry(...);<br> using (entry)<br> {<br> &nbsp;&nbsp;&nbsp; if (entry.Properties.Contains(&quot;property&quot;))<br> &nbsp;&nbsp;&nbsp; {<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //now safe to access &quot;property&quot;<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //cast as appropriate to string, byte[], int, etc...<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //object o = entry.Properties[&quot;property&quot;].Value;<br> &nbsp;&nbsp;&nbsp; }<br> }</font><br> <br> This same pattern is appropriate for the SearchResult as well:<br> <br> <font face="Courier New" size="2">SearchResult result;<br> //... fill the result<br> <br> if (result.Properties.Contains(&quot;property&quot;))<br> {<br> &nbsp;&nbsp;&nbsp; //now safe to access &quot;property&quot;<br> &nbsp;&nbsp;&nbsp; //cast as appropriate to string, byte[], int, etc...<br> &nbsp;&nbsp;&nbsp; //object o = result.Properties[&quot;property&quot;][0];<br> }<br> <br> </font>Depending on the attribute, the PropertyValueCollection (or ResultPropertyValueCollection) will return either a single value or multiple values.&nbsp; To actually get a value from the DirectoryEntry, we need to cast the 'object' to whatever type we are expecting.&nbsp; Thus, we get something like:<br> <br> <font face="Courier New" size="2">DirectoryEntry entry = new DirectoryEntry(...);<br> using (entry)<br> {<br> &nbsp;&nbsp;&nbsp; if (entry.Properties.Contains(&quot;</font><font face="Courier New" size="2">sn</font><font face="Courier New" size="2">&quot;))<br> &nbsp;&nbsp;&nbsp; {<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; string lastName = entry.Properties[&quot;sn&quot;][0].ToString();<br> &nbsp;&nbsp;&nbsp; }<br> }</font><br> <br> In this example, I cast the 'sn' attribute to the lastname.&nbsp; It makes 2 assumptions: 1.) The value can be interpreted as a string, and 2.) I am only interested in a single valued attribute.<br> <br> The pattern changes slightly if we are looking to read the values from a multi-valued attribute:<br> <br> <font face="Courier New" size="2">DirectoryEntry entry = new DirectoryEntry(...);<br> using (entry)<br> {<br> &nbsp;&nbsp;&nbsp; if (entry.Properties.Contains(&quot;</font><font face="Courier New" size="2">mail</font><font face="Courier New" size="2">&quot;))<br> &nbsp;&nbsp;&nbsp; {<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; foreach (object o in entry.Properties[&quot;mail&quot;])<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //iterate through each value in the 'mail' attribute as a string<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Response.Output.Write(&quot;mail: {0}&quot;, o.ToString());<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br> &nbsp;&nbsp;&nbsp; }<br> }</font><br> <br> Notice that I am iterating through all the objects and casting to string in this case.&nbsp; If the attribute was something else, it might be appropriate to cast to a byte[] array or an integer, etc.&nbsp; It just depends on what it is, but the casting from object to whatever type you need is up to you.<br> <br> <font size="4"><i><font color="#0000ff">Binding to a Data Control:</font></i></font><br> <br> In order to bind to things like a dropdown list or a datagrid, we need to perform the search first and manually create a datasource to use.&nbsp; Since AD is hierarchical and datacontrols are not, we generally need to think about how we will model the data when rows/columns won't make any sense.<br> <br> Here is a sample function that will search Active Directory given a filter for searching and the attributes to retrieve.&nbsp; I called it 'FindUsers' for lack of a better name about 5 years ago and decided not to change it to confuse people.&nbsp; In reality, it can be used to find anything, not just users.&nbsp; It returns a DataSet and optionally caches it for faster lookups next time.<br> <br> <font face="Courier New" size="2">public DataSet FindUsers(string sFilter, string[] columns, string path, bool useCached)<br> {<br> &nbsp;&nbsp;&nbsp; //try to retrieve from cache first<br> &nbsp;&nbsp;&nbsp; HttpContext context = HttpContext.Current;<br> &nbsp;&nbsp;&nbsp; DataSet userDS = (DataSet)context.Cache[sFilter];<br> &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; <br> &nbsp;&nbsp;&nbsp; if((userDS == null) || (!useCached))<br> &nbsp;&nbsp;&nbsp; {<br> &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; //setup the searching entries<br> &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; DirectoryEntry deParent = new DirectoryEntry(path);<br> &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; //deParent.Username = Config.Settings.UserName;<br> &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; //deParent.Password = Config.Settings.Password;<br> &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; deParent.AuthenticationType = AuthenticationTypes.Secure;<br> &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; <br> &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; DirectorySearcher ds = new DirectorySearcher(<br> &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; deParent,<br> &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; sFilter,<br> &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; columns,<br> &nbsp;&nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp; SearchScope.Subtree<br> &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; );<br> <br> &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; ds.PageSize = 1000;<br> &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; <br> &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; using(deParent)<br> &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; {<br> &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; //setup the dataset that will store the results<br> &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; userDS = new DataSet(&quot;userDS&quot;);<br> &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; DataTable dt = userDS.Tables.Add(&quot;users&quot;);<br> &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; DataRow dr;<br> &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; <br> &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; //add each parameter as a column<br> &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; foreach(string prop in columns)<br> &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; {<br> &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; dt.Columns.Add(prop, typeof(string));<br> &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; }<br> <br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; using (SearchResultCollection src = ds.FindAll())<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br> &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; foreach(SearchResult sr in src)<br> &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br> &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; dr = dt.NewRow();<br> &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; foreach(string prop in columns)<br> &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br> &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if(sr.Properties.Contains(prop))<br> &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br> &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; dr[prop] = sr.Properties[prop][0];<br> &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br> &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br> &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; dt.Rows.Add(dr);<br> &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br> &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; }<br> &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; //cache it for later, with sliding 3 minute window<br> &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; context.Cache.Insert(sFilter, userDS, null, DateTime.MaxValue, TimeSpan.FromSeconds(180));<br> &nbsp;&nbsp;&nbsp; }<br> &nbsp;&nbsp;&nbsp; return userDS;<br> }</font> <br> <br> Now, just place a datagrid (or dropdown) on your page, and with a few lines of code, you have a searcher:<br> <br> <font face="Courier New" size="2">//sample use<br> string qry = String.Format(&quot;(&amp;(objectCategory=person)(givenName={0}*))&quot;, txtFirstName.Text);<br> string[] columns = new string[]{&quot;givenName&quot;, &quot;sn&quot;, &quot;cn&quot;, &quot;sAMAccountName&quot;, &quot;telephoneNumber&quot;, &quot;l&quot;}<br> string ldapPath = &quot;LDAP://dc=mydomain&quot;;<br> <br> DataSet ds = FindUsers(qry, columns, ldapPath, true);<br> DataGrid1.DataSource = ds;<br> DataGrid1.DataBind();</font> <br> <br> I will try to keep this thread updated with the most common questions.<br> 2005-08-02T15:46:26-04:00