<?xml version="1.0" encoding="UTF-8" ?>
<?xml-stylesheet type="text/xsl" href="http://devlicio.us/utility/FeedStylesheets/rss.xsl" media="screen"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/" xmlns:wfw="http://wellformedweb.org/CommentAPI/"><channel><title>Alan Northam : AutoComplete</title><link>http://devlicio.us/blogs/alan_northam/archive/tags/AutoComplete/default.aspx</link><description>Tags: AutoComplete</description><dc:language>en</dc:language><generator>CommunityServer 2008.5 SP1 (Build: 31106.3070)</generator><item><title>Practical Implementation - spending some cache.</title><link>http://devlicio.us/blogs/alan_northam/archive/2008/03/17/practical-implementation-spending-some-cache.aspx</link><pubDate>Tue, 18 Mar 2008 01:35:00 GMT</pubDate><guid isPermaLink="false">40756a8b-6212-4073-9d98-6c26781577de:39697</guid><dc:creator>anortham</dc:creator><slash:comments>1</slash:comments><description>&lt;p&gt;Take a look at my &lt;a href="http://devlicio.us/blogs/alan_northam/archive/2008/03/06/cold-hard-cache.aspx" target="_blank"&gt;previous post&lt;/a&gt; for the details on a cache helper method that I like to use.&amp;nbsp; In this post, we&amp;#39;ll be putting it to work (the word for today is J - O - B).&amp;nbsp; Here&amp;#39;s a look at the code for reference.&lt;/p&gt;
&lt;p&gt;
using System;
using System.Reflection;
using System.Web;
using System.Web.Caching;
using log4net;

namespace CacheManagement
{
    public delegate bool Factory&amp;lt;T&amp;gt;( out T instance );
    public class CacheHelper
    {
        private static readonly ILog log =
           LogManager.GetLogger(
               MethodBase.GetCurrentMethod().DeclaringType );

        private static readonly Cache cache = HttpRuntime.Cache;
        private CacheHelper() { }

        public static T GetFromCache&amp;lt;T&amp;gt;( string key, int cacheTimeInMinutes, Factory&amp;lt;T&amp;gt; retrieveMethod ) where T : class
        {
            T target = cache[key] as T;
            if ( target == null )
            {
                log.Info( &amp;quot;Cache miss for key:&amp;quot; + key + &amp;quot;  type:&amp;quot; + typeof( T ) );
                if ( retrieveMethod( out target ) )
                {
                    cache.Insert( key, target, null, DateTime.Now.AddMinutes( cacheTimeInMinutes ),
                        Cache.NoSlidingExpiration, CacheItemPriority.Normal, OnRemove );
                }
            }
            else
            {
                log.Info( &amp;quot;Cache hit for key:&amp;quot; + key + &amp;quot;  type:&amp;quot; + typeof( T ) );
            }
            return target;
        }

        public static void OnRemove( string key, object cacheItem, CacheItemRemovedReason reason )
        {
            log.Info( &amp;quot;Object removed from cache: Key-&amp;quot; + key + &amp;quot;: Reason-&amp;quot; + reason );
        }
    }
}
&lt;/p&gt;
&lt;p&gt;
Enter the &lt;a href="http://asp.net/AJAX/AjaxControlToolkit/Samples/AutoComplete/AutoComplete.aspx" target="_blank"&gt;ASP.NET AJAX AutoComplete Extender&lt;/a&gt;.&amp;nbsp; It&amp;#39;s a perfect candidate for a little cache help.&amp;nbsp; I certainly wouldn&amp;#39;t want the database being hit every time a user typed the next letter in the autocomplete box.&amp;nbsp; I apologize for wearing you down with all this talk.&amp;nbsp; I think I&amp;#39;ve said too much.&amp;nbsp; Let&amp;#39;s look at the code for autocomplete webservice.&lt;/p&gt;
&lt;p&gt;
using log4net;
using System.Reflection;
using System.Web.Services;
using System.Collections.Generic;
using CacheManagement;

[WebService( Namespace = &amp;quot;http://tempuri.org/&amp;quot; )]
[WebServiceBinding( ConformsTo = WsiProfiles.BasicProfile1_1 )]
[System.Web.Script.Services.ScriptService]
public class AutoCompleteWebService : WebService
{
    private static readonly ILog log =
       LogManager.GetLogger(
           MethodBase.GetCurrentMethod().DeclaringType );

    private static readonly int cacheTTL = 15;
    private static readonly int minimumPrefixLength = 3;

    public AutoCompleteWebService()
    {

        //Uncomment the following line if using designed components 
        //InitializeComponent(); 
    }

    [WebMethod]
    public string[] GetWordList( string prefixText, int count )
    {
        //minimumPrefixLength should never be greater than prefixText.Length
        //this is a sanity check to insure the minimumPrefixLength field was set corretly
        string prefixKey = prefixText.Substring( 0,
            minimumPrefixLength &amp;gt; prefixText.Length
                ? prefixText.Length : minimumPrefixLength );

        //get the working list
        List&amp;lt;string&amp;gt; searchList = CacheHelper.GetFromCache&amp;lt;List&amp;lt;string&amp;gt;&amp;gt;(
            prefixKey,
            cacheTTL,
            delegate( out List&amp;lt;string&amp;gt; instance )
            {
                return DataAccess.GetWordList( prefixKey, out instance );
            } );

        log.Debug( &amp;quot;searchList full count: &amp;quot; + searchList.Count );

        //narrow the results if the prefixText is longer than prefixKey
        if ( prefixText.Length &amp;gt; prefixKey.Length )
        {
            searchList = searchList.FindAll(
                delegate( string s )
                {
                    return s.Substring( 0, prefixText.Length &amp;gt; s.Length
                        ? s.Length : prefixText.Length ) == prefixText;
                } );
        }

        //only return at maximum the number of results requested by count
        if ( searchList.Count &amp;gt; count )
            searchList = searchList.GetRange( 0, count );

        return searchList.ToArray();
    }
}
&lt;/p&gt;
&lt;p&gt;The webservice takes the first part of the prefixText (minimumPrefixLength should match the value in the autocomplete extender control) and queries the database for all matches.&amp;nbsp; It stores that result set in cache and then filters the list by prefixText if needed.&amp;nbsp; Finally it will take the top X (count is specified by the control) and return it to the control.&lt;/p&gt;
&lt;p&gt;If a user was searching for &amp;quot;automobile&amp;quot;, after typing a-u-t all possible results would be cache so that each successive letter will not hit the database.&amp;nbsp; Another database hit will be incurred when a user searches for a different 3 letter prefix.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;I&amp;#39;m attaching the full sample code along with a Sql Express database that contains some huge word list I found online (to be attached later, technical difficulties).&amp;nbsp; Enjoy and try not to spend it all in one place.&lt;/p&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;&lt;img src="http://devlicio.us/aggbug.aspx?PostID=39697" width="1" height="1"&gt;</description><category domain="http://devlicio.us/blogs/alan_northam/archive/tags/Featured/default.aspx">Featured</category><category domain="http://devlicio.us/blogs/alan_northam/archive/tags/Cache/default.aspx">Cache</category><category domain="http://devlicio.us/blogs/alan_northam/archive/tags/AJAX/default.aspx">AJAX</category><category domain="http://devlicio.us/blogs/alan_northam/archive/tags/AutoComplete/default.aspx">AutoComplete</category><category domain="http://devlicio.us/blogs/alan_northam/archive/tags/HttpRuntime.Cache/default.aspx">HttpRuntime.Cache</category></item></channel></rss>