Section 11.2. Adding Autocomplete to a Control


11.2. Adding Autocomplete to a Control

Web applications are becoming more and more like desktop applications, and the use of Ajax technologies has fueled this trend. One feature that desktop applications have, but web sites usually don't, is the autocomplete feature: whenever you enter something in a text box, the application looks up data suitable for the field (for instance, within most browsers, a list of previously entered data in similar fields) and offers to autofill the field for you.

One of the first well-known web applications to support such a feature is Google Suggest (http://www.google.com/webhp?complete=1&hl=en). Whenever you start typing in the text field, the web page not only suggests popular search terms, but also shows approximately how many results this search may turn up, as shown in Figure 11-3. Of course, by now you know how this is done: an XMLHttpRequest is sent to a web service, which returns search terms and the estimated number of results.

Figure 11-3. Google Suggest


Atlas provides a control extender called AutoCompleteExtender that serves just this purposeit looks up data in the background and then suggests this data for a form element. One of the issues in implementing this is coding the CSS and JavaScript necessary to display the suggestions, make 225hem keyboard-navigable, and so on. With Atlas, this work has already been done, and you just have to apply this feature. Note, though, that some of the more tricky bits of Google Suggest (including the keyboard navigation) are not fully implemented in the Atlas extender.

From a web control point of view, the only element for which autocompletion makes sense is TextBox. So, here is the element:

 <asp:TextBox  runat="server"></asp:TextBox> 

Then, the Atlas control must be included: AutoCompleteExtender. Within this element, the AutoCompleteProperties element is used to configure the autocompletion effect. The following element attributes are supported:


Enabled

Whether to activate the effect (set to "TRue") or not


TargetControlID

The ID of the control you want to make "autocompleteable"


ServicePath

The path to the web service that generates the autocompletion data


ServiceMethod

The method of the web service that you call to get autocompletion data

Here is the appropriate markup for this example:

 <atlas:AutoCompleteExtender runat="server">   <atlas:AutoCompleteProperties     Enabled="true"     ServicePath="Vendors.asmx" ServiceMethod="GetVendors"     TargetControl /> </atlas:AutoCompleteExtender> 

Example 11-2 shows how to create an ASP.NET page with a text box that supports autocompletion.

Example 11-2. Adding autocompletion to a text box

 AutoComplete.aspx <%@ Page Language="C#" %> <!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  runat="server">   <title>Atlas</title> </head> <body>   <form  runat="server">     <atlas:ScriptManager  runat="server">     </atlas:ScriptManager>     <asp:TextBox  runat="server"></asp:TextBox>     <input type="button" value="Display Information"            onclick="window.alert('not implemented!');" />     <atlas:AutoCompleteExtender runat="server">       <atlas:AutoCompleteProperties         Enabled="true"         ServicePath="Vendors.asmx" ServiceMethod="GetVendors"         TargetControl />     </atlas:AutoCompleteExtender>   </form> </body> </html> 

Finally, you need to implement the web service that retrieves the data. To do so, you must be aware of the method signature of the method to call. Here is the signature:

 public string[] <MethodName>(string PrefixText, int count) 

So the method gets two parameters with rather obvious meanings:


prefixText

The text the user enters into the text field, which must be the prefix of all matches


count

The maximum number of results to be returned

The return data must be an array of string, so unfortunately, you cannot use a dataset or something similar here.

Exploring Data Sent by Atlas

Sniffing the XMLHttpRequest call may help finding out which data Atlas sends to the server, as you can see in Figure 11-4, and is also a helpful measure in exploring the inner workings of Atlas further when working with server components.


Figure 11-4. Tools such as Live HTTP headers reveal the signature


In the example web service that we will use for the autocompletion data, the AdventureWorks database is queried; to be exact, the company names of all vendors are returned. As usual, you may have to adapt the connection string to your local systemin the code, we assume that the SQL Server 2005 Express Edition is available using Windows authentication at (local)\SQLEXPRESS.

To begin, the web service code checks the search term. For the sake of simplicity, only letters from a to z (both upper- and lowercase) are allowed. This data check is mandatory to avoid SQL injection, because the code must execute a search query with LIKE. As an alternative, you could use a parametrized query.

Avoiding SQL Injection

SQL injection is one of the most dangerous security vulnerabilities in web applications today. The issue arises when dynamic data from the user is used to construct a SQL query. For instance, have a look again at the string concatenation in the vendors web service that generates the SQL command:

 SqlCommand comm = new SqlCommand(   "SELECT TOP " +     count +     " Name FROM Purchasing.Vendor WHERE Name LIKE '" +     PrefixText +     "%'",   conn); 

Now imagine that count is a string, not an integer, or that PrefixText is not being checked for "dangerous" values. This code could turn out dangerous. There are several possibilities to exploit this, but imagine the following value for PrefixText:

 ' OR 2>1 -- 

Then, the SQL command will look similar to this:

 SELECT TOP 10 Name FROM Purchasing.Vendor WHERE Name LIKE '' OR 2 > 1 -- %' 

This would return the first 10 entries of the table, not just the first 10 that match some specific letter. There are other, far more dangerous exploits.

Usually, you can prevent this attack by using prepared statements: you use placeholders for all user-suplied values in the WHERE clause and later fill the placeholders with user-supplied data. Unfortunately, this does not work with our specific query because we have to append the % wildcard character to the user data. (As an alternative, use a prepared stataement and append the % wildcard character to the value of the placeholderremember to check the placeholder data for special characters like % and _.) Therefore, the code first checks PrefixText and exits when any characters are found that are not allowed.


If you use a regular expression to validate input data, consider allowing foreign-language characters such as German umlauted letters or French accented letters. The characters you do not want to accept, because they are "dangerous" from a security point of view, are single quotes, double quotes, square brackets, underscore characters, double hyphens, semicolons, and percent characters. These characters (and their encoded versions) all have a special meaning within the query. That's why it's better to validate user input using a whitelist approach (allow a predefined set of valid input) rather than a blacklist approach (disallow a predefined set of invalid input).

The web service also checks the count parameter provided to the method to make sure it is a positive number and not greater than 100, so as not to provide an easy way to launch denial-of-service (DoS) attacks.

This is what the web service code looks like that performs these validations:

 using System.Text.RegularExpressions; ... [WebMethod] public string[] GetVendors(string PrefixText, int count) {   Regex regex = new Regex("^[a-zA-Z ]*$");   if (!regex.IsMatch(PrefixText) || count < 1 || count > 100)   {     return null;   } 

After the data is validated, the SQL query is dynamically assembled and sent to the database. A typical query would look like this:

 SELECT TOP 10 Name FROM Purchasing.Vendor WHERE NAME LIKE 'Int%' 

This assumes that count has the value 10 (which is, coincidentally, the value Atlas sends by default) and the user typed Int into the text field. Here is the complete code for the database query, including filling the results into a dataset:

   SqlConnection conn = new SqlConnection(     "server=(local)\\SQLEXPRESS; Integrated Security=true; Initial Catalog=AdventureWorks");   conn.Open();   SqlCommand comm = new SqlCommand(     "SELECT TOP " +       count +       " Name FROM Purchasing.Vendor WHERE Name LIKE '" +       PrefixText +       "%'",     conn);   SqlDataAdapter adap = new SqlDataAdapter(comm);   DataSet ds = new DataSet();   adap.Fill(ds); 

Then the data must be transformed into a string array. This array must not contain more than count elements (a call to Math.Min() will ensure that only count elements are returned if the database contains more elements).

This can easily be achieved using a for loop:

   string[] vendors = new string[Math.Min(count, ds.Tables[0].Rows.Count)];   for (int i = 0; i < Math.Min(count, ds.Tables[0].Rows.Count); i++)   {     vendors[i] = ds.Tables[0].Rows[i].ItemArray[0].ToString();   }   return vendors; } 

Example 11-3 shows the complete code for implementing this web service.

Example 11-3. A web service that retrieves possible matches

 Vendors.asmx <%@ WebService Language="C#"  %> using System; using System.Web; using System.Web.Services; using System.Web.Services.Protocols; using System.Data; using System.Data.SqlClient; using System.Text.RegularExpressions; [WebService(Namespace = "http://hauser-wenz.de/")] [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)] public class Vendors : System.Web.Services.WebService {   [WebMethod]   public string[] GetVendors(string PrefixText, int count)   {     Regex regex = new Regex("^[a-zA-Z ]*$");     if (!regex.IsMatch(PrefixText) || count < 1 || count > 100)     {       return null;     }     SqlConnection conn = new SqlConnection(       "server=(local)\\SQLEXPRESS; Integrated Security=true; Initial Catalog=AdventureWorks");     conn.Open();     SqlCommand comm = new SqlCommand(       "SELECT TOP " +         count +         " Name FROM Purchasing.Vendor WHERE Name LIKE '" +         PrefixText +         "%'",       conn);     SqlDataAdapter adap = new SqlDataAdapter(comm);     DataSet ds = new DataSet();     adap.Fill(ds);     string[] vendors = new string[Math.Min(count, ds.Tables[0].Rows.Count)];     for (int i = 0; i < Math.Min(count, ds.Tables[0].Rows.Count); i++)     {       vendors[i] = ds.Tables[0].Rows[i].ItemArray[0].ToString();     }     return vendors;   } } 

And now it is time to try this in the browser. Load the page and enter a few letters, at least threewith two or fewer letters, Atlas does not issue a web service call. If some matches are found, they are displayed with little delay in the text box.

With the web service outlined in Example 11-3, caching may be of great use, especially when the same terms are searched over and over again. In set caching, just change the WebMethod attribute of GetVendors() to include a cache duration value:

 [WebMethod(CacheDuration = 60)]. 

The CacheDuration value is measured in seconds, so the preceding attribute would cache the web service's results for one minute.


If you are using Microsoft SQL Server as the database backend (as in this example), you can also create a SqlCacheDependency on the DataSet objects (for details, see the "For Further Reading" section).


If you do not get any results, try this: there are several companies whose name begins with the word "International", so entering that word should get you a rewarding number of matches. Figure 11-5 shows you some typical results.

Figure 11-5. Atlas is suggesting vendor names


Instead of using a control extender, you could use a behavior. Behaviors in general were already covered in Chapter 6; here is the xml-script markup you can use to attach this behavior to a text box (which has as in Example 11-2):

 <script type="text/xml-script">   <page xmlns:script="http://schemas.microsoft.com/xml-script/2005">     <components>       <control >         <behaviors>            <autocomplete             serviceURL="Vendors.asmx"              serviceMethod="GetVendors"               minimumPrefixLength="3"             completionSetCount="10" />          </behaviors>        </control>      </components>    </page>  </script> 


Note that the URL of the web service providing the autocompletion data is put in a property called serviceURL, whereas the AutoCompleteExtender control used a property called ServicePath.

Generally, using the extender is more intuitive andsince you have IntelliSense support in Visual Studio/Visual Web Developerless error-prone.




Programming Atlas
Programming Atlas
ISBN: 0596526725
EAN: 2147483647
Year: 2006
Pages: 146

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net