Understanding AJAX and ASP.NET

  • 5/15/2010

Extender Controls

The UpdatePanel provides a way to update only a portion of the page. That’s pretty amazing. However, AJAX’s compelling features have a very broad reach. One of the most useful features is the extender control architecture.

Extender controls target existing controls to extend functionality in the target. Whereas controls such as the ScriptManager and the Timer do a lot in terms of injecting script code into the page as the page is rendered, the extender controls often manage the markup (HTML) in the resulting page.

The following subsections discuss the ASP.NET AJAX extender controls. The first one is the AutoComplete extender.

The AutoComplete Extender

The AutoComplete extender attaches to a standard ASP.NET TextBox. As the end user types text in the TextBox, the AutoComplete extender calls a Web service to look up candidate entries based on the results of the Web service call. The following example borrows a component from Chapter 15, “Application Data Caching”—the quotes collection containing a number of famous quotes by various people.

Using the AutoComplete extender

  1. Add a new page to AJAXORama. Because this page will host the AutoComplete extender, name it UseAutocompleteExtender.

  2. Add an instance of the ScriptManager control to the page you just added.

  3. Borrow the QuotesCollection class from Chapter 15. Remember, the class derives from System.Data.Table and holds a collection of famous quotes and their originators. You can add the component to AJAXORama by right-clicking the project node, selecting Add Existing Item, and locating the QuotesCollection.cs file associated with the UseDataCaching example in Chapter 15.

  4. Add a method to retrieve the quotes based on the last name. The method should accept the last name of the originator as a string parameter. The System.Data.DataView class you use for retrieving a specific quote is useful for performing queries on a table in memory. The method should return the quotes as a list of strings. There might be none, one, or many, depending on the selected quote author. You use this function shortly.

    using System;
    using System.Data;
    using System.Configuration;
    using System.Web;
    using System.Web.Security;
    using System.Web.UI;
    using System.Web.UI.WebControls;
    using System.Web.UI.WebControls.WebParts;
    using System.Web.UI.HtmlControls;
    using System.Collections.Generic;
    
    /// <summary>
    /// Summary description for QuotesCollection
    /// </summary>
    public class QuotesCollection : DataTable
    {
       public QuotesCollection()
       { }
    
       public void Synthesize()
       {
          this.TableName = "Quotations";
          DataRow dr;
    
          Columns.Add(new DataColumn("Quote", typeof(string)));
          Columns.Add(new DataColumn("OriginatorLastName", typeof(string)));
          Columns.Add(new DataColumn(@"OriginatorFirstName",
                typeof(string)));
    
          dr = this.NewRow();
          dr[0] = "Imagination is more important than knowledge.";
          dr[1] = "Einstein";
    
          dr[2] = "Albert";
          Rows.Add(dr);
         // Other quotes added here...
       }
    
       public string[]
       GetQuotesByLastName(string strLastName)
       {
           List<string> list = new List<string>();
    
           DataView dvQuotes = new DataView(this);
           string strFilter = String.Format("OriginatorLastName = '{0}'", strLastName);
           dvQuotes.RowFilter = strFilter;
    
           foreach (DataRowView drv in dvQuotes)
           {
               string strQuote =
                   drv["Quote"].ToString();
    
               list.Add(strQuote);
           }
    
           return list.ToArray();
       }
    }
  5. Add a class named QuotesManager to the project. The class manages caching. The caching example from which this code is borrowed stores and retrieves the QuotesCollection during the Page_Load event. Because the QuotesCollection will be used within a Web service, the caching has to happen elsewhere. To do this, add a public static method named GetQuotesFromCache to retrieve the QuotesCollection from the cache:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    
    
    /// <summary>
    /// Summary description for QuotesManager
    /// </summary>
    public class QuotesManager
    {
       public QuotesManager()
       {
       }
    
    
        public static QuotesCollection GetQuotesFromCache()
        {
            QuotesCollection quotes;
            quotes =
                (QuotesCollection)HttpContext.Current.Cache["quotes"];
    
            if (quotes == null)
            {
                quotes = new QuotesCollection();
                quotes.Synthesize();
            }
            return quotes;
        }
    }
  6. Add an XML Web Service to your application. Right-click the project and add an ASMX file to your application. Name the service QuoteService. You can remove the WebService and WebServiceBinding attributes, but be sure to adorn the XML Web Service class with the [System.Web.Script.Services.ScriptService] attribute by uncommenting it (Visual Studio put it in for you). That way, it is available to the AutoComplete extender later on. The AutoCompleteExtender uses the XML Web Service to populate its drop-down list box.

  7. Add a method to get the last names of the quote originators—that’s the method that populates the drop-down box. The method should take a string representing the text already typed in as the first parameter, an integer representing the maximum number of strings to return. Grab the QuotesCollection from the cache using the QuoteManager’s static method GetQuotesFromCache. Use the QuotesCollection to get the rows from the QuotesCollection. Finally, iterate through the rows and add the originator’s last name to the list of strings to be returned if it starts with the prefix passed in as the parameter. The Common Language Runtime (CLR) String type includes a method named StartsWith that’s useful to figure out whether a string starts with a certain prefix. Note that you also have to add using statements for generic collections and data as shown:

    using System;
    using System.Linq;
    using System.Web;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.Web.Services;
    
    using System.Data;
    
    
    [System.Web.Script.Services.ScriptService]
    public class QuoteService : System.Web.Services.WebService
    {
        [WebMethod]
        public string[]
        GetQuoteOriginatorLastNames(string prefixText,
                                    int count)
    
        {
            List<string> list = new List<string>();
    
            QuotesCollection quotes =
                QuotesManager.GetQuotesFromCache();
    
            prefixText = prefixText.ToLower();
    
            foreach (DataRow dr in quotes.Rows)
            {
                string strName =
                    dr["OriginatorLastName"].ToString();
    
                if (strName.ToLower().StartsWith(prefixText))
                {
                    if (!list.Contains(strName))
                    {
                        list.Add(strName);
                    }
                }
            }
    
            return list.GetRange(0,
                System.Math.Min(count, list.Count)).ToArray();
        }
    }
  8. Now drop a TextBox on the UseAutocompleteExtender page to hold the originator’s last name to be looked up. Give the TextBox an ID of TextBoxOriginatorLastName.

  9. Drag an AutoCompleteExtender from the AJAX Toolbox and add it to the page. Set its ID to be AutoCompleteExtenderForOriginatorLastName. Point the AutoComplete TargetControlID to the TextBox holding the originator’s last name, TextBoxOriginatorLastName. Make the MinimumPrefix length 1, the ServiceMethod GetQuoteOriginatorLastNames, and the ServicePath quoteservice.asmx. This wires up the AutoComplete extender so that it takes text from the TextBoxOriginatorLastName TextBox and uses it to feed the XML Web Service GetQuoteOriginatorLastNames method.

    <cc1:AutoCompleteExtender
        ID="AutoCompleteExtenderForOriginatorLastName"
        TargetControlID="TextBoxOriginatorLastName"
        MinimumPrefixLength="1"
        ServiceMethod="GetQuoteOriginatorLastNames"
        ServicePath="quoteservice.asmx"
        runat="server">
    </cc1:AutoCompleteExtender>
  10. Add a TextBox to the page to hold the quotes. Name the TextBox TextBoxQuotes.

  11. Update the Page_Load method. It should look up the quotes based on the name in the text box by retrieving the QuotesCollection and calling the QuotesCollection GetQuotesByLastName method:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.Web.UI;
    using System.Web.UI.WebControls;
    using System.Text;
    
    public partial class UseAutocompleteExtender :
    System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            QuotesCollection quotes =
               QuotesManager.GetQuotesFromCache();
            string[] quotesArray =
               quotes.GetQuotesByLastName(TextBoxOriginatorLastName.Text);
    
            if (quotesArray != null && quotesArray.Length > 0)
            {
                StringBuilder str = new StringBuilder();
                foreach (string s in quotesArray)
                {
                    str.AppendFormat("{0}\r\n", s);
                }
                this.TextBoxQuotes.Text = str.ToString();
            }
            else
            {
                this.TextBoxQuotes.Text = "No quotes match your request.";
            }
        }
    }
  12. To make the page updates more efficient, drop an UpdatePanel onto the page. Put the TextBox for holding the quotes in the UpdatePanel. This causes only the TextBox showing the quotes to be updated instead of performing a whole-page refresh. Add a button following the originator’s last name TextBox with the ID ButtonFindQuotes.

  13. Add two asynchPostBack triggers to the UpdatePanel. The first trigger should connect the TextBoxOriginatorLastName TextBox to the TextChanged event. The second trigger should connect the ButtonFindQuotes button to the button’s Click event.

    The following graphic shows the layout of the page using the AutoCompleteExtender in action:

    httpatomoreillycomsourcemspimages605039.png
  14. Run the page. As you type originator names into the TextBox, you should see a drop-down list appear containing candidate names based on the QuotesCollection’s contents.

The AutoComplete extender is an excellent example of the capabilities that ASP.NET AJAX support includes. Internet Explorer has had an autocomplete feature built in for quite a while. Internet Explorer remembers often-used names of HTML input text tags and recent values that have been used for them. For example, when you go online to buy an airline ticket and then go back to buy another one later, watch what happens as you type in the Web address. The Internet Explorer autocomplete feature makes available a drop-down list below the address bar that shows the last few addresses you’ve typed in that begin with the same text you began typing in the text box.

The ASP.NET AutoComplete extender works very much like this. However, the major difference is that the end user sees input candidates generated by the Web site rather than simply a history of recent entries. Of course, the Web site could mimic this functionality by tracking a user’s profile identity and store a history of what a particular user has typed in to a specific input field on a page. The actual process of generating autocomplete candidates is completely up to the Web server, giving a whole new level of power and flexibility in programming user-friendly Web sites.

A Modal Pop-up Dialog-Style Component

AJAX provides another interesting feature that makes Web applications appear more like desktop applications: the ModalPopup extender. Historically, navigating a Web site involves users walking down the hierarchy of a Web site and climbing back out. When users provide inputs as they work with a page, the only means available to give feedback about the quality of the input data has been the validation controls. In addition, standard Web pages have no facility to focus users’ attention while they type in information.

Traditional desktop applications usually employ modal dialog boxes to focus user attention when gathering important information from the end user. The model is very simple and elegant: The end user is presented with a situation in which he or she must enter some data and then click OK or Cancel before moving on. After dismissing the dialog box, the end user sees exactly the same screen he or she saw right before the dialog box appeared. There’s no ambiguity and no involved process where the end user must walk up and down some arbitrary page hierarchy.

This example shows how to use the pop-up dialog extender control. You create a page with some standard content and then have a modal dialog-style pop-up window appear right before the page is submitted.

Using a ModalPopup extender

  1. Add a new page to AJAXORama to host the pop-up extender. Call it UseModalPopupExtender.

  2. As with all the other examples using AJAX controls, drag a ScriptManager from the Toolbox onto the page.

  3. Add a title to the page (the example here uses “ASP.NET Code of Content”). Give the banner some prominence by surrounding it with <h1> and </h1> tags. You can simply replace the existing <div> tag with the <h1> tag.

  4. Drag a Panel from the Toolbox onto the page to hold the page’s normal content.

  5. Add a Button to the Panel for submitting the content. Give the Button the ID ButtonSubmit and the text Submit and create a button Click event handler. You need this button later.

  6. Place some content on the panel. The content in this sample application uses several check boxes that the modal dialog pop-up examines before the page is submitted.

    <h1 >ASP.NET Code Of Conduct </h1>
    
    <asp:Panel ID="Panel1" runat="server"
        style="z-index: 1;left: 10px;top: 70px;
        position: absolute;height: 213px;width: 724px;
        margin-bottom: 0px;">
    <asp:Label ID="Label1" runat="server"
        Text="Name of Developer:"></asp:Label>
        &nbsp;<asp:TextBox ID="TextBox1"
        runat="server"></asp:TextBox>
    
    <br />
    <br />
    <br />
    As an ASP.NET developer, I promise to
    <br />
    <input type="checkbox" name="Check" id="Checkbox1"/>
    <label for="Check1">Use Forms Authentication</label>
    <br />
    <input type="checkbox" name="Check" id="Checkbox2"/>
    <label for="Check2">Separate UI From Code</label>
    <br />
    <input type="checkbox" name="Check" id="Checkbox3"/>
    <label for="Check3">Take Advantage of Custom Controls</label>
    <br />
    <input type="checkbox" name="Check" id="Checkbox4"/>
    <label for="Check4">Use AJAX</label>
    <br />
    <input type="checkbox" name="Check" id="Checkbox5"/>
    <label for="Check5">Give MVC a try</label>
    <br />
    <input type="checkbox" name="Check" id="Checkbox6"/>
    <label for="Check6">Give Silverlight a try</label>
    <br />
    
    <asp:Button ID="ButtonSubmit" runat="server" Text="Submit"
            onclick="ButtonSubmit_Click" />
    <br />
    </asp:Panel>
  7. Add another Panel to the page to represent the pop-up. Give this Panel a light yellow background color so that you’ll be able to see it when it comes up. It should also have the ID PanelModalPopup.

  8. Add some content to the new Panel that’s going to serve as the modal pop-up. At the very least, the pop-up should have OK and Cancel buttons. Give the OK and Cancel buttons the ID values ButtonOK and ButtonCancel. You need them a bit later, too.

    <asp:Panel ID="PanelModalPopup" runat="server"
        BorderColor="Black"
        BorderStyle="Solid"
        BackColor="LightYellow" Height="72px"
        Width="403px">
        <br />
        <asp:Label
           Text="Are you sure these are the correct entries?"
           runat="server">
        </asp:Label>
        &nbsp;&nbsp;&nbsp;&nbsp;
        <asp:Button ID="ButtonOK"
           runat="server"
           Text="OK" />
        &nbsp;&nbsp;
        <asp:Button ID="ButtonCancel"
        runat="server" Text="Cancel" />
        <br />
    </asp:Panel>
  9. Add a script block to the ASPX file. You need to do this by hand. Write functions to handle the OK and Cancel buttons. The example here examines check boxes to see which ones have been selected and then displays an alert to show which features have been chosen. The Cancel handler simply displays an alert indicating that the Cancel button was clicked:

    <script type="text/javascript">
    
        function onOk() {
            var optionsChosen;
            optionsChosen = "Options chosen: ";
    
            if($get('Checkbox1').checked)
            {
              optionsChosen =
                 optionsChosen.toString() +
                 "Use Forms Authentication ";
            }
    
            if($get('Checkbox2').checked)
            {
              optionsChosen =
                 optionsChosen.toString() +
                 "Separate UI From Code ";
            }
    
            if($get('Checkbox3').checked)
            {
              optionsChosen =
                 optionsChosen.toString() +
                 "Take Advantage of Custom Controls ";
            }
    
            if($get('Checkbox4').checked)
            {
              optionsChosen =
                 optionsChosen.toString() +
                 "Give AJAX a try ";
            }
            alert(optionsChosen);
        }
    
        function onCancel() {
           alert("Cancel was pressed");
        }
    </script>
  10. Drag the ModalPopup extender from the Toolbox onto the page.

  11. Add the following markup to the page to set various properties on the new ModalPopup extenders.s This sets the OkControIID property to ButtonOK and the CancelControlID property to ButtonCancel. It also sets the OnCancelScript property to onCancel() (the client-side Cancel script handler you just wrote). Set OnOkScript="onOk()” (the client-side OK script handler you just wrote). Finally, the following markup sets the TargetControlID property to ButtonSubmit:

    <cc1:ModalPopupExtender
        ID="ModalPopupExtender1"
        runat="server"
        OkControlID="ButtonOK"
        CancelControlID="ButtonCancel"
        OnCancelScript="onCancel()"
        OnOkScript="onOk()"
        TargetControlID="ButtonSubmit"
        PopupControlID="PanelModalPopup"
        runat="server"
        DynamicServicePath="" Enabled="True">
    </cc1:ModalPopupExtender>

    This graphic shows the layout of the page using the ModalPopup extender in Visual Studio 2010:

    httpatomoreillycomsourcemspimages605041.png
  12. Run the page. When you click the Submit button, the Panel designated to be the modal pop-up window is activated. (Remember, the Submit button is the TargetControlID of the ModalPopup Extender.) When you dismiss the pop-up window by clicking OK or Cancel, you should see the client-side scripts being executed. The following graphic image shows the ModalPopup extender displaying the modal pop-up window:

    httpatomoreillycomsourcemspimages605043.png