Thursday, 12 October 2006
Query Active Directory using Atlas (AJAX)
Last updated : 12th October 2006
Background
There is a lot of buzz around about AJAX and Microsoft's 'wrapper' technology referred to as Atlas. This is still in development but it's stable enough to use, certainly in my environment. This tutorial uses the CTP edition of the Ajax Library.
We will build a tool that allows users to find individuals on an Active Directory(AD) and then retrieve additional information from their AD Entry
Strategy
To make this as slick as possible we will Atlas enable the solution. What we want is for the user to start typing the individuals name and the application should then retrieve/filter and suggest valid entries as they type. When they locate the correct entry they will then click a button to retrieve the required information about the user back from Active Directory. This should update a section of the page without a full postback.
The first thing you need is to have the Atlas libraries installed. You can download the installation files from http://atlas.asp.net. After installation you will have a new project template as shown below. This adds the Microsoft.Web.Atlas.dll and relevant web.config entries that are necessary to 'bridge' out to the web service.
I've had a few emails requesting C# instead of VB.Net, so this tutorial is in C# but you can download a VB version as well. In order to achieve this AJAX functionality we will use 2 server controls from the Atlas library - UpdatePanel and the AutoCompleteExtender. See below.
Atlas Toolbar in VS 2005
Implementation
Lets have a look at what the final result will look like. I have kept the styling and error handling to a minimum for clarity.
To achieve this we require a single web page and a web service with a single method call.
Web Service
<%@ WebService Language="C#" Class="ADValidate" %>
using System;
using System.Web;
using System.Web.Services;
using System.Web.Services.Protocols;
using System.DirectoryServices;
using System.Configuration;
using System.Collections.Generic;
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class ADValidate : System.Web.Services.WebService {
[WebMethod]
public string[] GetUserNames(string prefixText, int count)
{
// Entry to LDAP objects e.g. LDAP://name.server.com/OU=UsersOU=Managed Objects,DC=server,DC=com
DirectoryEntry de = new DirectoryEntry(ConfigurationManager.AppSettings.Get("ADPath"));
de.Username = ConfigurationManager.AppSettings.Get("ADServiceAccount");
de.Password = ConfigurationManager.AppSettings.Get("ADServiceAccountPassword");
de.AuthenticationType = AuthenticationTypes.FastBind;
DirectorySearcher ds = new DirectorySearcher(de);
ds.ClientTimeout = TimeSpan.FromSeconds(30);
ds.PropertiesToLoad.Add("displayName");
ds.SizeLimit = 100;
SortOption srt;
srt = new SortOption("displayName", SortDirection.Ascending);
ds.Sort = srt;
ds.Filter = ("(displayName=" + prefixText.Trim() + "*)");
SearchResultCollection src;
src = ds.FindAll();
List<String> CandidateList = new List<string>();
foreach (SearchResult sr in src)
{
foreach (string Candidate in sr.Properties["displayName"])
{
CandidateList.Add(Candidate);
}
}
return CandidateList.ToArray();
}
}
The web service accepts 2 parameters (prefixText and count). The web service is responsible for returning an Array of strings back to the JavaScript calling function and this is used to populate the filtered list of matching users. Although the calling mechanism is JavaScript there is no client side coding required as the Atlas server controls take care of the necessary JavaScript that is emitted based on the web client (IE, Firefox etc).
Inside the web service we basically create a searcher object that will query the Active Directory using the System.DirectoryServices namespace (Remember to add it into your solution as a reference). Note the asterisk (*) at the end of the filter, this acts like a wild card. We also make sure that the list comes back sorted. We are only interested in returning the 'displayName' entry in the Active Directory structure so this is the only property we load prior to execting the FindAll() call.
After the call I am simply looping through the results and adding them to a generic collection of strings or simply returning an empty string array if no entries match.
UI - Web Page
<%@ Page Language="C#" %>
<%@ Import Namespace="System.DirectoryServices" %>
<%@ Import Namespace="System.Configuration" %>
<%@ Import Namespace="System.Data" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
<title></title>
<style type="text/css">
body {
font-family: Arial, Verdana;
font-size: 11pt;
font-color: #000000;
margin: 0;
padding: 0 10px 0 10px;
text-align: left
}
</style>
<atlas:ScriptManager ID="AtlasScriptCore" runat="server" EnablePartialRendering="true" />
</head>
<body>
<form id="Form1" runat="server">
<div id="content">
<h3>
Active Directory Searcher</h3>
<div id="searchbox">
Type Users Surname<br />
<asp:TextBox ID="txtFullName" runat="server">
</asp:TextBox>
<asp:Button ID="btnDetails" runat="server" Text="Get AD Details" OnClick="GetADDetails" />
<atlas:AutoCompleteExtender runat="server" ID="autoComplete1">
<atlas:AutoCompleteProperties
TargetControlID="txtFullName"
Enabled="True"
ServicePath="ADValidate.asmx"
ServiceMethod="GetUserNames"
MinimumPrefixLength="1" />
</atlas:AutoCompleteExtender>
</div><br />
<div id="Results">
<atlas:UpdatePanel ID="UpdatePanel1" runat="server" Mode="Conditional">
<ContentTemplate>
<asp:GridView ID="ADUserProperties" runat="server">
</asp:GridView>
<asp:Literal runat="server" ID="SysMessage" EnableViewState="false" ></asp:Literal>
</ContentTemplate>
<Triggers>
<atlas:ControlEventTrigger ControlID="btnDetails" EventName="Click" />
</Triggers>
</atlas:UpdatePanel>
</div>
</div>
</form>
</body>
</html>
<script runat="server">
protected void GetADDetails(Object sender , EventArgs e )
{
// Take the value from the input box and pull back a few AD details
DataTable UserProperties = null;
UserProperties = GetUserByDisplayName(txtFullName.Text);
// Display data if we have any or show warning
if (UserProperties != null)
{
ADUserProperties.DataSource = UserProperties;
ADUserProperties.DataBind();
}
else
{
// Show no records
SysMessage.Text = "<p style=\"color: red;\">Could not find any details for this user. <br />" +
"Please check that the users name is correct.</p>";
}
}
protected DataTable GetUserByDisplayName(String fullUserName)
{
DirectoryEntry de = new DirectoryEntry(ConfigurationManager.AppSettings.Get("ADPath"));
// Authentication details
de.Username = ConfigurationManager.AppSettings.Get("ADServiceAccount"); //DOMAIN\User
de.Password = ConfigurationManager.AppSettings.Get("ADServiceAccountPassword");
de.AuthenticationType = AuthenticationTypes.FastBind;
DirectorySearcher DirectorySearcher = new DirectorySearcher(de);
DirectorySearcher.ClientTimeout = TimeSpan.FromSeconds(30);
// load the properties we are interested in
DirectorySearcher.PropertiesToLoad.Add("cn");
DirectorySearcher.PropertiesToLoad.Add("sAMAccountName");
DirectorySearcher.PropertiesToLoad.Add("mail");
DirectorySearcher.PropertiesToLoad.Add("displayName");
DirectorySearcher.PropertiesToLoad.Add("mDBStorageQuota");
DirectorySearcher.PropertiesToLoad.Add("title");
DirectorySearcher.PropertiesToLoad.Add("physicalDeliveryOfficeName");
DirectorySearcher.PropertiesToLoad.Add("telephoneNumber");
// filter it on exact entry - NOTE no wild card
DirectorySearcher.Filter = "(displayName=" + fullUserName.Trim() + ")";
SearchResult result;
// There should only be one entry
result = DirectorySearcher.FindOne();
if (result != null)
{
// Create a table an populate it with properties to bind to gridview
DataTable myTable = new DataTable("ActiveDir");
myTable.Columns.Add(new DataColumn("Key", System.Type.GetType("System.String")));
myTable.Columns.Add(new DataColumn("Value", System.Type.GetType("System.String")));
DataRow myRow;
foreach (string propname in result.Properties.PropertyNames)
{
foreach (Object objValue in result.Properties[propname])
{
myRow = myTable.NewRow();
myRow[0] = propname;
myRow[1] = objValue.ToString();
myTable.Rows.Add(myRow);
}
}
return myTable;
}
else
{
return null;
}
}
</script>
Note the entries for the Atlas controls. I'm only setting properties, there is no need to write any Javascript as the Atlas framework is responsible for this. The UpdatePanel is updated when we click on the 'Get AD Details' button. You can see that I have set this in the <Triggers> section of the Update Panel. Also note that I set a property on the script manger to enable partial page rendering - EnablePartialRendering="true"
For the AutoCompleteExtender, I tell the control where to go for the data (web service and method name), how many characters need to be typed before making the web service call and which control on the page is looking for the data. This is all done in a declarative fashion.
When the user selects a entry form the dropdown list and clicks on the 'Get AD Details' button we then execute another search on the Active directory but this time we are searching for only one entry. We use the .FindOne() method to bring back the reequired information, hence the additional PropertiesToLoad entries. Once we get these back I am simply creating a DataTable to return back for binding to a GridView in the standard fashion. See below.
Summary
Hopefully this tutorial has shown how you might query an Active Directory structure and how easy it then is to add AJAX functionality with Atlas Server controls.
If you download the samples remember to change the AppSettings section in the web.config file to match entries in your environment.
© Bracora 2013 . Powered by Bootstrap , Blogger templates and RWD Testing Tool