Pragmatic Developer

Ali Özgür

Bookmark Blog

Add to Technorati Favorites

Google Talk

Chat with Ali Özgür

Purchase PragmaSQL from

Calendar

«  December 2008  »
MoTuWeThFrSaSu
24252627282930
1234567
891011121314
15161718192021
22232425262728
2930311234
View posts in large calendar

Tag Cloud

Don't show

    Authors

    Recent Comments

    Banners




    Introduction

    Cuyahoga provides module developers with a simple to use wrapper classes for full text indexing with Lucene.NET.
    Although wrapper classes are easy to use these classes are not generic in nature. I mean
    we, as module developers, have to convert our custom content (be it static or dynamic)
    to a SearchContent object so that wrapper classes can index that content. Another similar limitation is when we perform search on the indexed content we can only get collection of SearchResult objects. I also want to mention that we, as module developers, have no control over which fields will be stored/unstored, which fields will be used as keywords or which fields will not be indexed while wrapper objects build the full text index.

    In this article I will try to show you how we can make Cuyahoga.Core fulltext indexing capabilities more generic by providing a seperate extension assembly.

    Background

    I created two web sites using Cuyahoga framework

    While developing these sites I needed to fulltext index some dynamic content, I mean content persisted in the database. After exploring the wrapper classes in Cuyahoga.Core.Search namespace I noticed the limitations of the present search architecture and decided to make these wrapper classes more generic by appliying .NET Generics and Reflection.

    Bo.Cuyahoga.Extensions.Search Namespace

    In order to extend Cuyahoga.Core.Search namespace functionality using .NET Generics and Reflection I copied all files to my project and started refactoring. I appended Ex to class names in order to distinguish extended wrapper classes from the original classes in Cuyahoga.Core.Search namespace.

    IndexBuilder<T> class

    Refactored generic version of Cuyahoga.Core.Search.IndexBuilder class. This class is
    used as a wrapper around Lucene.NET index building functionality. Take a closer look at BuildDocumentFromSearchContent DeleteContent methods. These methods were modified so that we can use reflection to retreive fields of content object.

     
    
    
    private Document BuildDocumentFromSearchContent(T searchContent)
    {
        Document doc = new Document();
        IList fields = SearchGenericUtils.GetSearchContentFields(typeof(T), searchContent);
        for(int i = 0; i < fields.Count ; i++)
        {
            SearchContentFieldInfo fi = fields[i];
            switch (fi.FieldType)
            {
                case SearchContentFieldType.Text:
                    doc.Add(Field.Text(fi.Name, fi.Value));
                    break;
                case SearchContentFieldType.UnStored:
                    doc.Add(Field.UnStored(fi.Name, fi.Value));
                    break;
                case SearchContentFieldType.UnIndexed:
                    doc.Add(Field.UnIndexed(fi.Name, fi.Value));
                    break;
                case SearchContentFieldType.Keyword:
                    doc.Add(Field.Keyword(fi.Name, fi.Value));
                    break;
                default:
                    break;
            }
        }
        return doc;
    } 
    
    
    public void DeleteContent(T searchContent)
    {
        if (this._rebuildIndex)
        {
            throw new InvalidOperationException("Cannot delete documents when rebuilding the index.");
        }
        else
        {
            this._indexWriter.Close();
            this._indexWriter = null; 
    
    
            // Search content key field uniquely identifies a document in the index.
            SearchContentFieldInfo ki = SearchGenericUtils.GetSearchContentKeyFieldInfo(typeof(T), searchContent);
            if (String.IsNullOrEmpty(ki.Name))
                throw new Exception("SearchContentKey Field not specified on target class!"); 
    
    
            Term term = new Term(ki.Name, ki.Value);
            IndexReader rdr = IndexReader.Open(this._indexDirectory);
            rdr.Delete(term);
            rdr.Close();
        }
    } 
    
    
    

    IndexQuery<T> class

    Refactored generic version of Cuyahoga.Core.Search.IndexQuery class. This class is used as a wrapper around Lucene.NET index search functionality. Find was modified to so that we can
    create instance of the content object and set property values via reflection.

     
    
    
    public SearchResultCollection<T> Find(string queryText, Hashtable keywordFilter, int pageIndex, int pageSize)
    {
        long startTicks = DateTime.Now.Ticks;
        //We get query fileds with reflection
        string[] qryFields = SearchGenericUtils.GetSearchContentQueryFields(typeof(T));
        if (qryFields.Length == 0)
            throw new Exception("No query field specified on target class!"); 
    
    
        Query query = MultiFieldQueryParser.Parse(queryText, qryFields, new StandardAnalyzer());
        IndexSearcher searcher = new IndexSearcher(this._indexDirectory);
        Hits hits;
        if (keywordFilter != null && keywordFilter.Count > 0)
        {
            QueryFilter qf = BuildQueryFilterFromKeywordFilter(keywordFilter);
            hits = searcher.Search(query, qf);
        }
        else
        {
            hits = searcher.Search(query);
        }
        int start = pageIndex * pageSize;
        int end = (pageIndex + 1) * pageSize;
        if (hits.Length() <= end)
        {
            end = hits.Length();
        }
        SearchResultCollection<T> results = new SearchResultCollection<T>();
        results.TotalCount = hits.Length();
        results.PageIndex = pageIndex; 
    
    
        //We get filds that will be populated as a result of the search via reflection
        string[] resultFields = SearchGenericUtils.GetSearchContentResultFields(typeof(T));
        for (int i = start; i < end; i++)
        {
          // We create instance of the target type with Activator and set field(property) values with reflection
            T instance = Activator.CreateInstance<T>();
            for (int j = 0; j < resultFields.Length; j++)
            {
                SearchGenericUtils.SetSearchResultField(instance, resultFields[j], hits.Doc(i).Get(resultFields[j]));
            } 
    
    
            //If target type implements ISearchResultStat we set Boost and Score properties of the instance
            if (instance is ISearchResultStat)
            {
                SearchGenericUtils.SetSearchResultField(instance, "Boost", hits.Doc(i).GetBoost());
                SearchGenericUtils.SetSearchResultField(instance, "Score", hits.Score(i));
            }
            results.Add(instance);
        } 
    
    
        searcher.Close();
        results.ExecutionTime = DateTime.Now.Ticks - startTicks;
        return results;
    } 
    
    
    

    SearchContentFieldAttribute Class

    .NET attributes are very neat implementation of declarative programming. We use SearchContentFieldAttribute to identify how we will perform fulltext indexing on our content. A field in our fulltext index directly maps to a property of our custom content type. For example if we want to index a Person class we specify which properties of this class will be indexed, how they are indexed and which properties will be set as a result of a fulltext index search with SearchContentFieldAttribute.

    SearchContentFieldAttribute is used to declare

    • Field Type: This declaration is needed because Lucene.NET has different field declarations. For details see Field class in
      Lucene.Net.Documents namespace
    • Key Field: Key field is used to uniquely identify our copntent. Only one key field declaration is allowed. If you declare two properties
      as key fields on the content class only the first one will be used.
    • Result Field: Content class properties with IsResultField=true will be automatically set after index search, other fields will not be set.
    • Query Field: Content class properties with IsQueryField=true will be used in index searches.Index search will be performed only on these
      properties

     
    
    
    namespace Bo.Cuyahoga.Extensions.Search
    {
        [AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
        public class SearchContentFieldAttribute : Attribute
        {
            private SearchContentFieldType _fieldType;
            /// 
            /// Default is Text
            /// 
            public SearchContentFieldType FieldType
            {
                get { return _fieldType; }
                set { _fieldType = value; }
            } 
    
    
            private bool _isResultField = true;
            /// 
            /// Default is true
            /// 
            public bool IsResultField
            {
                get { return _isResultField; }
                set { _isResultField = value; }
            } 
    
    
            private bool _isQueryField = false;
            /// 
            /// Default is false
            /// 
            public bool IsQueryField
            {
                get { return _isQueryField; }
                set { _isQueryField = value; }
            } 
    
    
            private bool _isKeyField = false;
            /// 
            /// Default is false
            /// 
            public bool IsKeyField
            {
                get { return _isKeyField; }
                set { _isKeyField = value; }
            } 
    
    
            public SearchContentFieldAttribute(SearchContentFieldType fieldType)
            {
                _fieldType = fieldType;
            } 
    
    
        } 
    
    
        public enum SearchContentFieldType
        {
            Text,
            UnStored,
            UnIndexed,
            Keyword
        }
    } 
    
    
    

    SearchResultCollection<T> Class

    This is the generic version of the SearchResultCollection class found in Cuyahoga.Core.Search namespace.Nothing special to mention about this class

    ISearchResultStat interface

    We use this interface in order to specify that our custom content type will hold query statistics like Boost and Score. If our custom content type implements this interface value of the Boost and Score properties are set after an index query.

     
    
    
    public interface ISearchResultStat
    {
        float Boost{get;set;}
        float Score{ get;set;}
    } 
    
    
    

    ReflectionHelper Singleton

    Reflection helper class designed to improve performance

     
    
    
    /// 
        /// Singleton reflection helper
        /// 
        public sealed class ReflectionHelper
        {
            #region Static Fields And Properties 
    
    
            static ReflectionHelper _instance = null;
            static readonly object _padlock = new object();
            public static ReflectionHelper Instance
            {
                get
                {
                    lock (_padlock)
                    {
                        if (_instance == null)
                        {
                            _instance = new ReflectionHelper();
                        }
                        return _instance;
                    }
                }
            }
            
            #endregion Static Fields And Properties 
    
    
            #region Instance Fields And Properties
            
            private Dictionary> _cache;
            
            #endregion Instance Fields And Properties 
    
    
            #region CTOR 
    
    
            private ReflectionHelper()
            {
                _cache = new Dictionary>();
            } 
    
    
            #endregion //CTOR 
    
    
            #region Reflection Helper Methods 
    
    
            public SearchContentFieldInfo[] GetKeyFields(Type t)
            {
                if (!_cache.ContainsKey(t))
                    AddTypeToCache(t); 
    
    
    
                List keyFields =  _cache[t].FindAll(
                    delegate(SearchContentFieldInfo fi)
                    {
                        return fi.IsKeyField == true;
                    });
                return keyFields.ToArray();
            } 
    
    
            public SearchContentFieldInfo[] GetKeyFields(Type t, object instance)
            {
                SearchContentFieldInfo[] fields = GetKeyFields(t);
                if (instance == null)
                    return fields; 
    
    
                GenericGetter getMethod;
                for (int i = 0; i < fields.Length; i++)
                {
                    getMethod = CreateGetMethod(fields[i].PropertyInfo);
                    object val = getMethod(instance);
                    fields[i].Value = val == null ? String.Empty : val.ToString();
                }
                return fields;
            } 
    
    
            public IList GetFields(Type t, object instance)
            {
                if (!_cache.ContainsKey(t))
                    AddTypeToCache(t); 
    
    
                Listresult = new List(_cache[t].ToArray()); 
    
    
                if (instance == null)
                    return result;
                
                GenericGetter getMethod;
                for (int i = 0; i < result.Count; i++)
                {
                    SearchContentFieldInfo fi = result[i];
                    getMethod = CreateGetMethod(fi.PropertyInfo);
                    object val = getMethod(instance);
                    fi.Value = val == null ? String.Empty : val.ToString();
                }
                return result;
            } 
    
    
            public string[] GetQueryFields(Type t)
            {
                if (!_cache.ContainsKey(t))
                    AddTypeToCache(t); 
    
    
                List < SearchContentFieldInfo > qryFields = _cache[t].FindAll(
                    delegate(SearchContentFieldInfo fi)
                    {
                        return fi.IsQueryField == true;
                    }); 
    
    
                
                List result = qryFields.ConvertAll(
                    delegate(SearchContentFieldInfo fi) 
                    {
                        return fi.Name;
                    }); 
    
    
                return result.ToArray();
            } 
    
    
            public string[] GetResultFields(Type t)
            {
                if (!_cache.ContainsKey(t))
                    AddTypeToCache(t); 
    
    
                List qryFields = _cache[t].FindAll(
                    delegate(SearchContentFieldInfo fi)
                    {
                        return fi.IsResultField == true;
                    }); 
    
    
    
                List result = qryFields.ConvertAll(
                    delegate(SearchContentFieldInfo fi)
                    {
                        return fi.Name;
                    }); 
    
    
                return result.ToArray();
            } 
    
    
            public void SetSearchResultField(string fieldName, object instance, object value)
            {
                Type t = instance.GetType();
                if (!_cache.ContainsKey(t))
                    AddTypeToCache(t);
                
                SearchContentFieldInfo field = _cache[t].Find(
                delegate(SearchContentFieldInfo fi)
                {
                    return fi.Name == fieldName;
                }); 
    
    
                if (field.Name != fieldName)
                    throw new Exception(String.Format("Field with name \"{0}\" not found on type \"{1}\"!", fieldName, t)); 
    
    
                GenericSetter setter = CreateSetMethod(field.PropertyInfo);
                setter(instance, Convert.ChangeType(value,field.PropertyInfo.PropertyType));
            } 
    
    
            #endregion //Reflection Helper Methods 
    
    
            #region Cache Operations
            private void AddTypeToCache(Type t)
            {
                if (_cache.ContainsKey(t))
                    return; 
    
    
                List fields = new List(); 
    
    
                PropertyInfo[] props = t.GetProperties(BindingFlags.Public | BindingFlags.Instance);
                for (int i = 0; i < props.Length; i++)
                {
                    PropertyInfo pi = props[i];
                    SearchContentFieldAttribute[] atts = (SearchContentFieldAttribute[])pi.GetCustomAttributes(typeof(SearchContentFieldAttribute), true);
                    if (atts.Length > 0)
                    {
                        SearchContentFieldInfo fi = new SearchContentFieldInfo();
                        fi.Name = pi.Name;
                        fi.FieldType = atts[0].FieldType;
                        fi.IsKeyField = atts[0].IsKeyField;
                        fi.IsResultField = atts[0].IsResultField;
                        fi.IsQueryField = atts[0].IsResultField;
                        fi.PropertyInfo = pi;
                        fields.Add(fi);
                    }
                }
                if (fields.Count > 0)
                    _cache.Add(t, fields);
            }
            #endregion //Cache Operations 
    
    
            #region Emit Getter/Setter 
    
    
            /* Source for CreateSetMethod and CreateGetMethod taken from
             * http://jachman.wordpress.com/2006/08/22/2000-faster-using-dynamic-method-calls/
             */
            private GenericSetter CreateSetMethod(PropertyInfo propertyInfo)
            {
                /*
                * If there’s no setter return null
                */
                MethodInfo setMethod = propertyInfo.GetSetMethod();
                if (setMethod == null)
                    return null; 
    
    
                /*
                * Create the dynamic method
                */
                Type[] arguments = new Type[2];
                arguments[0] = arguments[1] = typeof(object); 
    
    
                DynamicMethod setter = new DynamicMethod(
                    String.Concat("_Set", propertyInfo.Name, "_"),
                    typeof(void), arguments, propertyInfo.DeclaringType);
                ILGenerator generator = setter.GetILGenerator();
                generator.Emit(OpCodes.Ldarg_0);
                generator.Emit(OpCodes.Castclass, propertyInfo.DeclaringType);
                generator.Emit(OpCodes.Ldarg_1); 
    
    
                if (propertyInfo.PropertyType.IsClass)
                    generator.Emit(OpCodes.Castclass, propertyInfo.PropertyType);
                else
                    generator.Emit(OpCodes.Unbox_Any, propertyInfo.PropertyType); 
    
    
                generator.EmitCall(OpCodes.Callvirt, setMethod, null);
                generator.Emit(OpCodes.Ret); 
    
    
                /*
                * Create the delegate and return it
                */
                return (GenericSetter)setter.CreateDelegate(typeof(GenericSetter));
            } 
    
    
            ///
            /// Creates a dynamic getter for the property
            ///
            private static GenericGetter CreateGetMethod(PropertyInfo propertyInfo)
            {
                /*
                * If there’s no getter return null
                */
                MethodInfo getMethod = propertyInfo.GetGetMethod();
                if (getMethod == null)
                    return null; 
    
    
                /*
                * Create the dynamic method
                */
                Type[] arguments = new Type[1];
                arguments[0] = typeof(object); 
    
    
                DynamicMethod getter = new DynamicMethod(
                    String.Concat("_Get", propertyInfo.Name, "_"),
                    typeof(object), arguments, propertyInfo.DeclaringType);
                ILGenerator generator = getter.GetILGenerator();
                generator.DeclareLocal(typeof(object));
                generator.Emit(OpCodes.Ldarg_0);
                generator.Emit(OpCodes.Castclass, propertyInfo.DeclaringType);
                generator.EmitCall(OpCodes.Callvirt, getMethod, null); 
    
    
                if (!propertyInfo.PropertyType.IsClass)
                    generator.Emit(OpCodes.Box, propertyInfo.PropertyType); 
    
    
                generator.Emit(OpCodes.Ret); 
    
    
                /*
                * Create the delegate and return it
                */
                return (GenericGetter)getter.CreateDelegate(typeof(GenericGetter));
            }
            #endregion //Emit Getter/Setter
        } 
    
    
        public delegate void GenericSetter(object target, object value);
        public delegate object GenericGetter(object target); 
    
    
    

    SearchGenericUtils Static Class

     
    
    
    public class SearchContentFieldInfo
        {
            public string Name;
            public string Value;
            public SearchContentFieldType FieldType;
            public bool IsResultField;
            public bool IsQueryField;
            public bool IsKeyField;
            public PropertyInfo PropertyInfo;
        } 
    
    
        internal static class SearchGenericUtils
        {
            internal static SearchContentFieldInfo GetSearchContentKeyFieldInfo(Type t, object instance)
            {
                SearchContentFieldInfo[] keyFields =  ReflectionHelper.Instance.GetKeyFields(t,instance);
                
                if (keyFields.Length == 0)
                    throw new Exception(String.Format("No key filed defined for type {0}!", t));
                
                if(keyFields.Length > 1)
                    throw new Exception(String.Format("Only one key filed allowed for type {0}!", t)); 
    
    
                return keyFields[0];
            } 
    
    
            internal static SearchContentFieldInfo GetSearchContentKeyFieldInfo(Type t)
            {
                return GetSearchContentKeyFieldInfo(t, null);
            } 
    
    
            internal static IList GetSearchContentFields(Type t, object instance)
            {
                if (instance == null)
                    throw new Exception("Instance parameter is null!"); 
    
    
                return ReflectionHelper.Instance.GetFields(t, instance);
            } 
    
    
            internal static string[] GetSearchContentQueryFields(Type t)
            {
                return ReflectionHelper.Instance.GetQueryFields(t);
            } 
    
    
            internal static string[] GetSearchContentResultFields(Type t)
            {
                return ReflectionHelper.Instance.GetResultFields(t);
            } 
    
    
            internal static void SetSearchResultField(object instance , string fieldName, object value)
            { 
    
    
                if (instance == null)
                    throw new Exception("Object instance parameter is null!");
                if (String.IsNullOrEmpty(fieldName))
                    throw new Exception("Field name is empty!"); 
    
    
                ReflectionHelper.Instance.SetSearchResultField(fieldName, instance, value);    
            }
        } 
    
    
    

    Using The Sample Code

    Our sample console application simply creates three PersonContent objects and full text indexes these objects. Then we perform queries on the indexed content.

    In the sample application PersonContent class will be used as our custom content type.The class looks like

     
    
    
    public class PersonContent
    {
        private string _id; 
    
    
        [SearchContentField(SearchContentFieldType.Keyword,IsKeyField=true)]
        public string Id
        {
            get { return _id; }
            set { _id = value; }
        } 
    
    
        private string _keyword;
        [SearchContentField(SearchContentFieldType.Keyword)]
        public string Keyword
        {
            get { return _keyword; }
            set { _keyword = value; }
        } 
    
    
        private string _fullName;
        [SearchContentField(SearchContentFieldType.Text,IsQueryField=true)]
        public string FullName
        {
            get { return _fullName; }
            set { _fullName = value; }
        } 
    
    
        private string _notes;
        [SearchContentField(SearchContentFieldType.UnStored,IsResultField=false,IsQueryField=true)]
        public string Notes
        {
            get { return _notes; }
            set { _notes = value; }
        } 
    
    
        private int _age;
        [SearchContentField(SearchContentFieldType.UnIndexed)]
        public int Age
        {
            get { return _age; }
            set { _age = value; }
        } 
    
    
        public override string ToString()
        {
            StringBuilder sb = new StringBuilder();
            sb.AppendLine("Id = " + _id);
            sb.AppendLine("FullName = " + _fullName);
            sb.AppendLine("Age = " + _age.ToString());
            
            return sb.ToString();
        }
    } 
    
    
    

    NOTE: For details about the meanings of UnIndexed, UnStored, Text and Keyword read
    Lucene.NET documentation. SearchContentFieldType is a utility enumeration that provides us the ability to call appropriate methods of Field found in Lucene.Net.Documents namespace.

    Tips on Merging Extended Search Classes to Cuyahoga Core

    You do not need to merge these extensions directly to Cuyahoga.Core in order to use them.
    But if you want Cuyahoga.Core to handle full text indexing in a more generic way, you can simply replace IndexQuery, IndexBuilder, SearchCollection, ISearchable, IndexEventHandler and IndexEventArgs classes with their counterparts found in Bo.Cuyahoga.Extensions.Search namespace. And obviosuly you will have to refactor other parts using the old versions of the replaced classes. One final issue you must consider is that you will have to add attributes to SearchContent and SearchResult classes.

    Dependencies

    In order to compile this code you need

    • Cuyahoga.Core project
    • Lucene.Net.ddl
    • log4net.dll

    History

    • 27 March 2008:
      • Bug in DeleteContent method resolved
      • Exception is thrown in case of null getter or setter methods in ReflectionHelper class GetKeyFields, GetFields and SetSearchResultField methods
    • 07 March 2008: First version published
    • 10 March 2008:
      • ReflectionHelper class added for performance improvement.
      • SearchContentFieldInfo methods modified to call appropriate ReflectionHelper methods.
      • SearchContentFieldInfo is not a struct anymore.

    Posted in: .NET Development , CodeProject , Cuyahoga , Lucene.NET  Tags:

    Be the first to rate this post

    • Currently 0/5 Stars.
    • 1
    • 2
    • 3
    • 4
    • 5
    aliozgur posted on February 18, 2008 16:24

    Introduction

    Cuyahoga framework has very nice approach to Web development. It has bunch of built-in modules and you can develop your own modules in a
    couple of hours. If you have some experience with NHibernate and/or some other web framework your module development
    may even take less than an hour. My product site PragmaSQL Online runs on top of Cuyahoga framework and it took
    me just a couple of hours to bring this site up and running. Although Cuyahoga is a very nice framework and I love Cuyahoga development
    I shall admit that you may experience some problems while applying some advanced topics like Ajax to Cuyahoga. In this article
    I will show you a simple and structured way to add Ajax support to your Cuyahoga web site.

     

    Background

    I previously shared my module development experience with an article titled Developing Simple Issue Tracker Module here on CodeProject.
    In my issue tracker module I used AjaxToolkit ModalPopupExtender control. But this was an unstructured approach it was a kind of hack,
    I simply placed ScriptManager on my ASPX page and moved injection code of GeneralPage class to OnPreInit method from OnInit.
    This was the right choice and it saved the day, this module is still online and is working very well.

    Nowadays I am working on another web site BenimOdam.com (The web site is in Turkish and is not online yet due to domain name transfer issues).
    In BenimOdam.com we only use Forum and ContactUs built-in modules and much of the functionality is embedded in our own modules. These modules
    are mainly functioning as list and record editing modules. We used MultiView and View controls to provide tabbed browsing functionality and
    I guess much of you know that MultiView does a post back while switching between views and this post back may be annoying from the users point of view.
    In such uncomfortable situations UpdatePanel control included with Microsoft Ajax distribution(previously known as Atlas) provides a nice and easy to apply solution.
    You simply put your controls, MultiView in our case, inside an UpdatePanel and you are done. Your users will experience much more smooth
    navigation and probably they will be happier.

    Cuyahoga Internals

    Cuyahoga has some principles we must keep in mind before attempting to extend the framework for Ajax support. These are

    • Template user control with placeholders is used to identify different parts of your pages
    • Custom HttpHandler (PageHandler) is used to process requests and custom UrlWriter to rewrite raw urls.
    • Page structure(sections) and modules contained within the sections are resolved from the database
    • Injection is used to build the resulting pages.
    • Modules are designed as user Controls and you must inherit your module control from BaseModuleControl
    • You can use GeneralPage base class to build custom ASPX pages not releated to any Cuyahoga node that use the
      default site template.

    Ajax Support Preperation

    UpdatePanel included within Microsoft Ajax distirbution and AjaxToolkit controls all require a ScriptManager control placed
    as the first control in your ASPX page. But Cuyahoga does not handle your modules as seperate ASPX pages and injects
    your module inside the template you are using. As i mentioned above a template has placeholder controls that make the
    different parts of your pages. As a result it is not guaranteed that the ScripManager control you placed as the first
    control in your module will also be placed as the first control in the rendered page. Being the first control in the resulting
    page is a very tight constraint emposed by ASP.NET. Solution to this situation is placing a default ScriptManager control inside your
    template user controls as the first control. Here is a sample template user control code

     
    <%@ Control Language="c#" AutoEventWireup="false" Inherits="Cuyahoga.Web.UI.BaseTemplate" %> 
    <%@ Register TagPrefix="cc1" Namespace="Cuyahoga.ServerControls.Navigation" 
       Assembly="Cuyahoga.ServerControls.Navigation" %> 
    <%@ Register Assembly="System.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
      Namespace="System.Web.UI" TagPrefix="asp" %> 
    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> 
    <html> 
    <head> 
        <title> <asp:literal id="PageTitle" runat="server"> </asp:literal> </title> 
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> 
        <asp:literal id="MetaTags" runat="server" /> 
        <asp:literal id="Stylesheets" runat="server" /> 
        <!--[if IE]> 
        <style type="text/css" media="screen"> 
            body { behavior: url(<%= Page.ResolveUrl("~/csshover.htc") %> ); /* call hover behaviour file */ } 
        </style> 
        <![endif]--> 
    </head> 
    <body> 
        <form id="t" method="post" runat="server"> 
            <asp:ScriptManager ID="DefaultScriptManager" runat="server"> </asp:ScriptManager> 
            <div id="container">             
                <div id="header">     
                    <div id="logo"> 
                     <img width="120px" height="100px"  alt="BenimOdam.com" 
                    src="http://www.mydomain.com/Templates/Bo/Images/bo_logosmaller.gif%22/> 
                    </div> 
                    <div> 
                        <span id="titletext"> Ev arkadaşı ve ev arayanların buluşma noktası.</span> 
                    </div>         
                    <div id="searcharea"> 
                        <asp:placeholder id="searchinput" runat="server"> </asp:placeholder> 
                    </div> 
                </div> 
                <div id="nav"> 
                    <cc1:menu id="mnuMain" runat="server" MenuCss = "~/Templates/Bo/Css/Menu.css"> </cc1:menu> 
                </div>     
    
    
                <!-- shadow divs --> 
                <div id="containerleft"> 
                <div id="containertopleft"> 
                <div id="containerright"> 
                <div id="containertopright"> 
                <!-- main --> 
                <div id="main"> 
                    <!--
                    <div id="globalmenu"> 
                        <asp:placeholder id="globalMenu" runat="server"> </asp:placeholder> 
                    </div> 
                    -->
    

    In the sample code above you can see that we added a ScriptManager named DefaultScriptManager as the first control
    of the form. That means DefaultScriptManager will be injected to all our Cuyahoga pages using this template. As a result we
    met the constraint saying that "Ajax controls need a ScriptManager and this script manager must be the first control in the ASPX page.".

    NOTE: Do not forget to register System.Web.Extensions.dll, else you will get an exception telling you that ScriptManager type can not be resolved.

    Adding Ajax Support To Your Modules

    In theory we do not have to add ScriptManager to our module code, since we added a default ScriptManager to our template which will
    automatically be injected to all of our Cuyahoga pages. But in practice we will probably want designer support while developing our modules
    and if you do not include ScriptManager in your module control the designer will complain and refuse to render the AJAX control
    which can make us feel uncomfortable (you can still design your module markup without designer support). When you add a ScriptManager
    to your module the resulting Cuyahoga page will contain more than one ScriptManager one from the template and one or many from your modules.
    Another constraint about ScriptManager says that "Only one ScriptManager can be used in a page" and we have to find a way to
    remove additional ScriptManager instances and leave only one ScriptManager in the resulting page. The solution here is straight forward
    we will remove ScriptManagers placed in our modules and leave only the DefaultScriptManager placed in our template control.

    LIMITATIONS If you want to use custom JavaScript code for AJAX handling in your module you have to register
    your scripts to your ScriptManager's (one included in your module code) Scripts collection. In this case you will have to rethink
    the solution I proposed. May be you will have to invent some interaction that places your custom scripts in the DefaultScriptManager
    before removing the ScriptManager from your module.

    As i mentioned above all of your Cuyahoga modules must be inherited from BaseModuleControl.But we have to find a way to remove
    ScriptManagers from our module code before they are injected to the template we are using. The solution is creating another base class ( AjaxBaseModuleControl)
    which supports ScripManager removal functionality. AjaxBaseModuleControl is inherited from BaseModuleControl and overrides AddedControl method.
    In the AddedControl function we try to catch the ScriptManager control after it is added to the Controls collection of our module and remove it from
    from the colllection so that multiple ScriptManagers problem is avoided.

    NOTE: We could prefer to modify PageEngine class so that we would inspect all controls and remove the ScriptManagers from the modules.
    But that would probably cause performance problems since we would have to loop with foreach on modules' Controls collection.May be some modules
    would not even use AJAX and that would be waste of time inspecting these modules for a ScriptManager control.
    ( Marker interfaces could be used to identify AJAX modules but still that would be wast of time to loop)

    Here is the AjaxBaseModuleControl code

     
    namespace Cuyahoga.Web.UI
    {
      public class AjaxBaseModuleControl:BaseModuleControl
      {
        protected override void AddedControl(Control control, int index)
        {
          if (control.GetType() == typeof(ScriptManager))
            this.Controls.RemoveAt(index);
          else
            base.AddedControl(control, index);
        }
      }
    }
    


    AjaxBaseModuleControl is simple and straightforward, we simply catch the ScriptManager after it is added to Controls collection
    and remove it from the collection, which enables us to avoid multiple ScriptManagers problem.

    IMPORTANT: Cuyahoga PageEngine class applies the template and injects your modules in the overriden OnInit function.
    I would recommend you to move OnInit code to the overriden OnPreInit function. That is not necessary for module level Ajax support
    but the reason will be more clear when I explain page level Ajax support.

    Adding Ajax Support To Your Nodeless Pages

    Modules are the primary mains of Cuyahoga development. But it is obvious that only modules may not meet all your requirements.
    For example you would list records with a module and deploy a seperate nodeless page for record editing. We call the record editing page
    nodeless because this page is not attached to any node in our site structure.For such cases Cuyahoga framework provides us a base
    class named GeneralPage. You inherit your nodeless page from GeneralPage and Cuyahoga automatically applies
    the default site template (CSS styles and structure of the page based on the default template) to your page. Actually
    your page code is injected not rendered.

    For nodeless page example please go to Pragma Issue Tracker and try to view an issue from the issue list. The page
    used to view a specific issue is a nodeless page and it is not included in the site structure we simply redirect to
    this page from our module and Cuyahoga injects the page code automatically.

    In order to add Ajax support to our nodeless pages we have to apply the same ideas.

    • Add default ScriptManager as the first control to the resulting page.
    • Automatically remove the ScriptManager, added during design time, before Cuyahoga injects our page's source


    First item was already applied by putting a default script manager to our template control. For the second item
    we create another base class named AjaxGeneralPage which is inherited from GeneralPage and
    override the AddedControl method to intercept and catch the ScriptManager included in our nodeless page.
    Here is the code:

     
    namespace Cuyahoga.Web.UI
    {
      public class AjaxGeneralPage:GeneralPage
      {
        protected override void AddedControl(Control control, int index)
        {
          if (control.GetType() == typeof(HtmlForm))
            TryToRemoveScriptManager(control as HtmlForm);
          else
            base.AddedControl(control, index);
        } 
        private void TryToRemoveScriptManager(HtmlForm frm)
        {
          int idx = -1;
          for (int i = 0; i <frm.Controls.Count; i++)
          {
            if (frm.Controls[i] is ScriptManager)
            {
              idx = i;
              break;
            }
          } 
          if (idx >= 0)
            frm.Controls.RemoveAt(idx);
        }
      }
    }  

    Please be warned that we do not catch the ScriptManager directly as that was the case in AjaxBaseModuleControl. We catch the
    HtmlForm control included within the page and search for ScriptManager in the form's Controls collection.

    IMPORTANT NOTE: Original version of GeneralPage class (base class of our AjaxGeneralPage) handles content
    loading in the overriden OnInit function (I think this was the only place in .NET 1.1 version to perform content loading).
    If you leave content loading code inside this function you will not be able to properly remove the ScriptManager control
    from your page. For details why this is not possible see ASP.NET Page Lifecylcle article on MSDN. To solve this problem
    we simply move the code in overriden OnInit function to OnPreInit override.That is the right place for content
    loading and dynamic control creation in .NET version 2.0.

    Our OnPreIniti function in GeneralPage class looks like this


     
    protected override void OnPreInit(EventArgs e)
    {
        // The GeneralPage loads it's own content. No need for the PageEngine to do that.
        base.ShouldLoadContent = false;
        
        //Init the PageEngine.
        //NOTE: We replaced base.OnInit with base.OnPreIniti
        base.OnPreInit(e);
        
        // Build page.
        ControlCollection col = this.Controls; 
    
        this._currentSite = base.RootNode.Site;
        if (this._currentSite.DefaultTemplate != null 
            && this._currentSite.DefaultPlaceholder != null 
            && this._currentSite.DefaultPlaceholder != String.Empty)
        {
            // Load the template
            this.TemplateControl = (BaseTemplate)this.LoadControl(UrlHelper.GetApplicationPath() 
                + this._currentSite.DefaultTemplate.Path); 
    
    
            // Register css
            string css = UrlHelper.GetApplicationPath() 
                + this._currentSite.DefaultTemplate.BasePath
                + "/Css/" + this._currentSite.DefaultTemplate.Css;
            RegisterStylesheet("maincss", css); 
    
    
            if (this._title != null)
            {
                this.TemplateControl.Title = this._title;
            } 
    
    
            // Add the pagecontrol on top of the control collection of the page
            this.TemplateControl.ID = "p";
            col.AddAt(0, this.TemplateControl); 
    
    
            // Get the Content placeholder
            this._contentPlaceHolder = this.TemplateControl.FindControl(this._currentSite.DefaultPlaceholder) as PlaceHolder;
            if (this._contentPlaceHolder != null)
            {
                // Iterate through the controls in the page to find the form control.
                foreach (Control control in col)
                {
                    if (control is HtmlForm)
                    {
                        // We've found the form control. Now move all child controls into the placeholder.
                        HtmlForm formControl = (HtmlForm)control;
                        while (formControl.Controls.Count > 0)
                        {    
                            this._contentPlaceHolder.Controls.Add(formControl.Controls[0]);                        
                        }
                    }
                } 
    
    
    
                // throw away all controls in the page, except the page control 
                while (col.Count > 1)
                {
                    col.Remove(col[1]);                
                }
            } 
    
    
            #region // Ali Ozgur (07-02-2008): Load sections that are related to the template
            foreach (DictionaryEntry sectionEntry in _currentSite.DefaultTemplate.Sections)
            {
                string placeholder = sectionEntry.Key.ToString();
                Section section = sectionEntry.Value as Section;
                if (section != null)
                {
                    BaseModuleControl moduleControl = CreateModuleControlForSection(section);
                    if (moduleControl != null)
                    {
                        ((PlaceHolder)this._templateControl.Containers[placeholder]).Controls.Add(moduleControl);
                    }
        &nb