Most applications have some sort of search capability built
into a site. This makes it convenient to quickly access related content that is
being sought for. Sometimes it can be helpful to see what has been searched for
in the past, along with how many instances of that search item have been found.
The following markup exists in the master page, a perfect
place to put a search textbox.
Listing 6
<asp:TextBox ID="txtSearch" runat="server" />
<ajax:TextBoxWatermarkExtender ID="extSearch" runat="server"
TargetControlID="txtSearch"
WatermarkText="Type to Search" />
<ajax:AutoCompleteExtender ID="extSearch2" runat="server"
TargetControlID="txtSearch"
ServicePath="~/Services/SearchService.asmx"
ServiceMethod="GetTopSearchPhrases"
MinimumPrefixLength="1" CompletionListItemCssClass="AutoCompleteItemStyle"
CompletionListHighlightedItemCssClass="AutoCompleteSelectedItemStyle"
OnClientItemSelected="autoComplete_itemSelected" />
<asp:LinkButton ID="lnkSearch" runat="server" CssClass="SiteLayoutMenuBarItem"
OnClick="lnkSearch_Click">Search</asp:LinkButton>
There is quite the number of .NET controls for implementing
a search. Outside of the textbox that retains search criteria and a linkbutton
that triggers the search, the two extenders help add on additional
functionality without requiring a custom control. The TextBoxWatermarkExtender
helps by providing a message to the user identifying the purpose of the search
textbox. The second extender, the AutoCompleteExtender, is a powerful extender
that queries the database, looking for existing search results and returning
the list.
The AutoCompleteExtender works by using a web service and
web method to get the items that match the text that the user has entered. The
web service has to conform to a specific signature for this to work, which I will
discuss in a moment.
As the user clicks the submit button, whatever is searched for
is logged to the database. There are several schemes that can be implemented to
track what the user searches for, and create a popularity ranking. The first
question you have to ask is shall whatever the user enters be logged or only
searches that have results? Should low to high volume results be
differentiated?
In this example, whatever the user enters is stored in the
database, but more ideally any search text that has actual results should be
stored. This could be done whenever loading the search page or search results.
Another question is what is the search querying? Is the
search using a full-text index in the database, index server, or some other
mechanism? This is important too, though I do not have a solution implemented
in this example; I am only focused on storing the user's query.
Let us start with the web service definition. As I mentioned
before, a web service used by the AutoCompleteExtender needs to conform to a
specific signature, shown below.
Listing 7
[WebMethod]
public string[] GetTopSearchPhrases(string prefixText, int count)
{
SearchManager manager = SearchManager.GetManager();
SearchPhraseCollection phraseCollection =
manager.GetTopSearchPhrases(prefixText, count);
return (from p in phraseCollection
select string.Format("{0} ({1} occurrence(s))", p.Phrase,
p.ResultCount.ToString())).ToArray();
}
The method takes two to three parameters, depending on the
configuration. In this example, the method takes the prefixText, which is the
characters the user has entered, and the count, the total number of items to
limit the search by. The latter value is specified in the body of the
AutoCompleteExtender; it is configurable by changing the CompletionSetCount
property value.
If the UseContextKey property of that extender is set to
true, a third parameter, contextKey, can be added as well. Notice that an array
of strings is returned to the caller. This is also required. Notice the
specialized output, which will include items in the format of "Toasters
(12 occurrence(s))."
As the user enters keys, the user is prompted with a
selection of items as shown in the screenshot below.
Figure 5
These entries can be selected using the mouse, or by clicking
the up and down arrow. Selecting an entry places the item in the textbox. These
entries are stored in the database using the following code in the search
button click event (next to the textbox, but not shown above).
Listing 8
protected void lnkSearch_Click(object sender, EventArgs e)
{
if (string.IsNullOrEmpty(this.txtSearch.Text))
return;
string text = this.txtSearch.Text.Trim();
if (string.IsNullOrEmpty(text))
return;
//Updates the search phrase count.
SearchManager manager = SearchManager.GetManager();
manager.UpdateSearchPhraseCount(text);
}
The SearchManager component makes a call to the data layer
to perform the update. If the searched text already exists, then the number of
occurrences is incremented by one; otherwise, a new entry is created. There is
only one problem not yet discussed. Upon selecting an item in the auto complete
popup, the text "(X occurrences)" would also be copied to the
textbox. This is not very useful, and needs to be stripped out.
The solution to this is to attach to the itemSelected client
event. Notice in the AutoCompleteExtender markup above, the OnClientItemSelected
references the name of an event handler. This event handler performs the work
of stripping out the "(X occurrences)" text from the selected entry. It
does this with the following JavaScript.
Listing 9
function autoComplete_itemSelected(sender, e)
{
var text = e.get_text();
if (text.indexOf('(') > 0)
{
text = text.substring(0, text.indexOf('('));
if (text != null && text.length > 0)
text = text.trimEnd();
}
var targetElement = sender.get_element();
targetElement.value = text;
}
The event argument in this event is
AutoCompleteItemEventArgs, which has an item, text, and value property. I am
using the text property getter, and parsing it to look for the first
parenthesis. If found, it is stripped out and assigned to the target element,
and is accessible using the element property getter. Remember that extenders
target another property and do not emit its own interface, so an extender's
element property references the extended control.
The issue that most people will have with the solution above
is that there is not any intellisense to find all this out, and there is not
any MSDN documentation. I had to manually dig through the AJAX control toolkit
source code to find all of this information. I would highly recommend looking
at the source code whenever using these extenders.