I've been working on a lot of back-end data access code lately and I
needed a little change. So when I arrived in the office last Friday I
boldly assigned a web-developer task to myself (its been at least year
since I wrote some serious ASP.NET)
This particular (very low priority) task was based on a customer
request for an email AutoSuggest TextBox. I sensed a Friday challenge.
In our web application we have a TextBox where the user can input
additional email addresses of the people they would like notified when
the workflow status of a particular process has changed.
For example, if this were an on-line shopping application (it is not),
this field would contain email addresses of people that would be
emailed when an item is now in stock. The email addresses need to be
semicolon delimited and in full smtp format.
For example:
client1@contoso.com;client2@contoso.com
The Change Request was simple, the TextBox should be replaced with a
control that acts like the 'To' field when composing an email in Outlook
In terms of behaviour: As a name is typed, the TextBox should
'autosuggest' matching contacts and when selected should display them
in the TextBox. It should obviously be able to accept multiple
contacts, in addition to contacts that do not exist (well, could not be
looked up). In this case the source of the contacts for lookup was the
corporate Active Directory (and not just users, but email distribution
groups as well). In addition, the control should display the full name
and not the underlying email address, just as outlook does.
I was pleasantly surprised how easy this was!
Since our Web Application is ASP.NET 4.0, we already use jQuery
throughout. With Google to rescue, I download and configured Drew
Wilsons jQuery autosuggest plugin:
http://code.drewwilson.com/entry/autosuggest-jquery-plugin
The plugin handles keydown events and will query a specified URL
supplying what the user has typed so far in the query string. In
response it expects a JSON formatted array of suggestions. This sounded
like a perfect match for a WCF WebService (which also turned out to be
surprisingly simple):
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Activation;
using System.ServiceModel.Web;
using System.Text.RegularExpressions;
using System.DirectoryServices;
namespace ExampleAutosuggest
{
[ServiceContract(Namespace = "")]
[ServiceKnownType(typeof(string))]
[ServiceKnownType(typeof(string[]))]
public interface IContactSuggestionService
{
[OperationContract]
[WebGet(UriTemplate = "/Search?q={q}", BodyStyle = WebMessageBodyStyle.Bare, ResponseFormat = WebMessageFormat.Json)]
Contact[] Suggest(string q);
}
public class ContactSuggestionService : IContactSuggestionService
{
public Contact[] Suggest(string q)
{
DirectoryEntry de = new DirectoryEntry();
de.Path = "LDAP://OU=Users,DC=prod,DC=ad,DC=contoso,DC=com";
de.AuthenticationType = AuthenticationTypes.Secure;
DirectorySearcher deSearch = new DirectorySearcher();
deSearch.SearchRoot = de;
deSearch.Filter = "(&(objectClass=user) (cn=" + q + "*))";
deSearch.SizeLimit = 10;
SearchResultCollection results = deSearch.FindAll();
// Get the SMTP email address for the users
IList list = new List();
foreach (SearchResult result in results) {
try {
DirectoryEntry der = result.GetDirectoryEntry();
string email = der.Properties["mail"].Value.ToString();
string name = der.Properties["name"].Value.ToString();
list.Add(new Contact(name, email));
}
catch (Exception) {
}
}
return list.ToArray();
}
}
[DataContract]
public class Contact
{
public Contact(string Name, string Email)
{
this.DisplayName = Name;
this.Email = Email;
}
[DataMember(IsRequired = true, Name = "name")]
public string DisplayName { get; set; }
[DataMember(IsRequired = true, Name = "value")]
public string Email { get; set; }
}
}
Download Project
Finally, I needed to make some changes to Drew's code so the control's state would survive both partial and full postbacks with ASP.NET. Drew's implementation saves the list of values to a hidden input field created by the script, this field only contains the underlying values, which may be different to what was displayed to the user and is only designed to be retrieved by the server during the PostBack. Once the Post Back all state is lost. This was address by adding a hidden form field to the page and supplying the elements id to the autosuggest code when it was attached. The autosuggest's code was then modified to serialise and deserialise its state (via JSON) to the hidden field whenever a contact was added or removed. The ASP.NET framework takes care of maintaining the value of the hidden field between postback and so the state of the autosuggest is also maintained. In the end we stored the serialised state of the auto-suggest directly in the database, which meant we could render it very quickly without having to perform Active Directory lookups on each contact before displaying the page. Im sure our DBA's would'nt be happy about this, but at least its not XML (which drives them crazy).
Download Customised Autosuggest
Note:
This Javascript is not perfect, but let me know if you find any bugs
0 comments:
Post a Comment