14.4. SiteSearch Revisited

Earlier in the book, the creation of an Ajax Site Search widget was discussed. That widget demonstrated a typical Ajax application that retrieves data from the server and displays that data with dynamically created HTML. In this section, this widget is revisited using the AJAX Extensions. This change alters many aspects of the original AjaxSiteSearch; the HTML structure changes to accommodate the inclusion of server controls, and a few adjustments to the C# code are required. Also, you won't write one line of JavaScript code.

14.4.1. The User Interface

The UI is still important, so the new UI doesn't change much from the original AjaxSiteSearch widget. The HTML structure, however, does change slightly due to the server controls. Also, this new version includes a new button to clear the search results. The resulting HTML looks like this:

<div class="ajaxSiteSearchContainer">
 <form class="ajaxSiteSearchForm">
   <span id="upTextBoxUpdate">
      <input type="text" id="txtSearchTerm" class="ajaxSiteSearchTextBox" />
   </span>
   <input type="submit" value="Go" id="btnSearch" class="ajaxSiteSearchButton" />
   <input type="submit" value="Clear" id="btnClear" class="ajaxSiteSearchButton" />
     <div class="ajaxSiteSearchResultPane">
      <div id="upResultsUpdate">
         <div id="plhResults">
            <a class="ajaxSiteSearchLink">Result Text</a>
         </div>
      </div>
   </div>
 </form>
</div>

Aside from the extra button, the three key changes are the <span/> and <div/> elements with the IDs of "upTextBoxUpdate", "upResultsUpdate", and the "plhResults". The <span/> and the first <div/> elements are added by the UpdatePanel controls. The second <div/> element ("plhResults") is added by another control called PlaceHolder; these controls reserve a location in the page, allowing the addition of future controls at runtime.

14.4.2. Getting Started

Open up Visual Studio and create a new ASP.NET AJAX–enabled web site. Set the language to Visual C#, and name the project ASPAjaxSiteSearch. Make sure that you save this web site in your web server's wwwroot directory. When Visual Studio creates and opens the project, you should be greeted with the default project. Default.aspx should be open, and you should have Default.aspx and Web.config in the Solution Explorer panel.

14.4.2.1. The Database Connection String

The first order of business is to edit the Web.config file so that it contains the database connection string. This version of the widget uses the same data as the original AjaxSiteSearch widget; therefore, the connection string will remain the same. Add the following <add/> element to the <connectionStrings/> element:

<add
    name="SiteSearch"
    connectionString="Data Source=localhost;
        Initial Catalog=BlogDatabase;User ID=sa;Password=pwd"
    providerName="System.Data.SqlClient"
/>

Don't forget to input your own credentials into this connection string.

14.4.2.2. Adding the Style Sheet

Next, right-click on the project's name in the Solution Explorer and create a new folder called css. Right-click on this folder and choose the "Add Existing Item..." option. Locate the ajaxsitesearch.css file created for the original widget and add it to the project. Although the HTML structure changes, the style sheet will not need any editing.

14.4.3. Declaring the Form

With Default.aspx open, switch over to Source view mode (the button is in the lower-left corner of the viewable area). Remove everything within the page's body and add the following HTML:

<div class="ajaxSiteSearchContainer">
    <form id="form1" class="ajaxSiteSearchForm" runat="server">

        <%-- More Code Here --%>

        <div class="ajaxSiteSearchResultPane">
            <%-- More Code Here --%>
        </div>
    </form>
</div>

Note that the opening <form/> tag has an attribute called runat. When set to "server," this attribute tells the ASP.NET engine that it needs to parse the element and its children.

The next step is to add the Go and Clear buttons. These are Button controls.

<div class="ajaxSiteSearchContainer">
    <form id="form1" class="ajaxSiteSearchForm" runat="server">

        <%-- More Code Here --%>

        <asp:Button CssClass="ajaxSiteSearchButton" ID="btnSearch" Text="Go"
            runat="server"  />
        <asp:Button CssClass="ajaxSiteSearchButton" ID="btnClear" Text="Clear"
            runat="server"  />

        <div class="ajaxSiteSearchResultPane">
            <%-- More Code Here --%>
        </div>
    </form>
</div>

Button controls have a variety of attributes available to use. In this code, the CssClass attribute is set to "ajaxSiteSearchButton". When the ASP.NET engine parses these controls, it transforms the CssClass attribute to the HTML class attribute. The next attribute, ID, serves two purposes. First, this attribute is used as the resulting <input/> element's id attribute, and second, it serves as an identifier when writing C# code. The Text attribute sets the button's text that is displayed to the user.

Now it's time to add the ScriptManager and UpdatePanel controls. This application uses two UpdatePanel controls. The first panel is for the TextBox control, and it is updated when the Clear button is clicked:

<div class="ajaxSiteSearchContainer">
    <form id="form1" class="ajaxSiteSearchForm" runat="server">
        <asp:ScriptManager ID="ScriptManager1" runat="server" />
        <asp:UpdatePanel ID="upTextBoxUpdate" RenderMode="Inline" runat="server">
            <ContentTemplate>
                <asp:Textbox CssClass="ajaxSiteSearchTextBox"
                            ID="txtSearchTerm" runat="server" />
            </ContentTemplate>
        </asp:UpdatePanel>

        <asp:Button CssClass="ajaxSiteSearchButton" ID="btnSearch" Text="Go"
            runat="server"  />
        <asp:Button CssClass="ajaxSiteSearchButton" ID="btnClear" Text="Clear"
            runat="server"  />

        <div class="ajaxSiteSearchResultPane">
            <%-- More Code Here --%>
        </div>
    </form>
</div>

The content of the first panel is the txtSearchTerm TextBox control; this is where users type in their search query. This UpdatePanel is set to update the TextBox control on every postback (remember, the UpdateMode attribute is optional and defaults to "Always").

The second UpdatePanel updates the search results. In the original AjaxSiteSearch widget, the results were appended to the <div/> element with a CSS class of "ajaxSiteSearchResultPane". This element still exists in this new version; however, the results are appended to a <div/> element inside of the original. This is due to the use of a PlaceHolder control:

<div class="ajaxSiteSearchContainer">
    <form id="form1" class="ajaxSiteSearchForm" runat="server">
        <asp:ScriptManager ID="ScriptManager1" runat="server" />
        <asp:UpdatePanel ID="upTextBoxUpdate" RenderMode="Inline" runat="server">
            <ContentTemplate>
                <asp:Textbox CssClass="ajaxSiteSearchTextBox"
                            ID="txtSearchTerm" runat="server" />
            </ContentTemplate>
        </asp:UpdatePanel>

        <asp:Button CssClass="ajaxSiteSearchButton" ID="btnSearch" Text="Go"
            runat="server"  />
        <asp:Button CssClass="ajaxSiteSearchButton" ID="btnClear" Text="Clear"
            runat="server"  />

        <div class="ajaxSiteSearchResultPane">
            <asp:UpdatePanel UpdateMode="Conditional" ID="upResultsUpdate"
                runat="server">
                <ContentTemplate>
                    <asp:PlaceHolder ID="plhResults" runat="server" />
                </ContentTemplate>
                <Triggers>
                   <asp:AsyncPostBackTrigger ControlID="btnSearch"
                       EventName="Click" />
                   <asp:AsyncPostBackTrigger ControlID="btnClear" EventName="Click"
/>
                </Triggers>
            </asp:UpdatePanel>
        </div>
    </form>
</div>

As mentioned earlier, PlaceHolder controls reserve space in the web page; this application uses this space by adding (and clearing) the search results. This control resides in the ContentTemplate of this UpdatePanel, which is updated when the Go and Clear Button controls' Click event fires. Clicking the btnSearch button fills the PlaceHolder with data, and the btnClear button clears that data.

14.4.4. Performing the Search

The search should execute when the Go button is clicked. The Click event handler, called btnSearch_Click(), resembles that of the AjaxSiteSearch.Search() method in the original widget. In fact, a good portion of that code is reused. The main changes result from adapting the code to use ASP.NET controls.

The btnSearch_click() event handler accepts two arguments. The first, sender, is the object that received the event. The second, e, is the event arguments that describe the event.

protected void btnSearch_Click(object sender, EventArgs e)
{
    //Get the search string
    string searchString = txtSearchTerm.Text;

    //more code here
}

The first step is to get the search string. Since ASP.NET controls can be used to get this information, it's not necessary to rely on fetching arguments out of the query string. To get the search term, use the Text property of the txtSearchTerm TextBox control. Then compare that value to an empty string. Doing so allows you to tell the user that he or she needs to enter text into the TextBox:

protected void btnSearch_Click(object sender, EventArgs e)
{
    //Get the search string
    string searchString = txtSearchTerm.Text;

    //Check to see if anything was entered.
    //If not, tell the user to enter text.
    if (searchString == String.Empty)
    {
        throw new Exception("Please enter a search term.");
    }
    //more code here
}

This code compares the searchString variable to an empty string. If it is true, this code throws a generic exception, which in turns fires the ScriptManager's AsyncPostBackError event (the handler will be written shortly).

If the user did enter text, a search should be performed with that text. This is where the code becomes familiar:

protected void btnSearch_Click(object sender, EventArgs e)
{
    //Get the search string
    string searchString = txtSearchTerm.Text;

    //Check to see if anything was entered.
    //If not, tell the user to enter text.
    if (searchString == String.Empty)
    {
        throw new Exception("Please enter a search term.");
    }
    else
    {

        //Get our connection string.
        string connectionString =
ConfigurationManager.ConnectionStrings["SiteSearch"].ConnectionString;
        //Build the query.
        string query = String.Format("SELECT TOP 10 BlogId, " +
            "Title FROM BlogPosts WHERE Post LIKE '%{0}%' " +

"OR Title LIKE '%{0}%' ORDER BY Date DESC", searchString);

        //Set up the database connection
        using (SqlConnection conn = new SqlConnection(connectionString))
        {
            //And get the command ready
            SqlCommand command = new SqlCommand(query, conn);
            //Open the connection.
            conn.Open();

            //Perform the query.
            using (SqlDataReader reader = command.ExecuteReader())
            {
                //If we got results...
                if (reader.HasRows)
                {
                    //Loop through them
                    while (reader.Read())
                    {
                        //more code here
                    }
                }
                else //No results found
                {
                    //more code here
                }
            }
        }
    }
}

This code is exactly like that from AjaxSiteSearch.Search(). The database connection string is obtained from the application's configuration, the query is built, a connection to the database is opened, and the query is executed against the database.

The difference this time around is in the data sent back to the client. The AjaxSiteSearch.Search() method returned a JSON string, which the client-side code used to create hyperlinks dynamically. In btnSearch_Click(), HyperLink controls will be added to the PlaceHolder. The end result is the same (links added to an HTML element), but this saves you from writing any JavaScript.

protected void btnSearch_Click(object sender, EventArgs e)
{
    //Get the search string
    string searchString = txtSearchTerm.Text;

    //Check to see if anything was entered.
    //If not, tell the user to enter text.
    if (searchString == String.Empty)
    {
        throw new Exception("Please enter a search term.");
    }
    else

{
        //Get our connection string.
        string connectionString =
ConfigurationManager.ConnectionStrings["SiteSearch"].ConnectionString;
        //Build the query.
        string query = String.Format("SELECT TOP 10 BlogId, " +
            "Title FROM BlogPosts WHERE Post LIKE '%{0}%' " +
            "OR Title LIKE '%{0}%' ORDER BY Date DESC", searchString);

        //Set up the database connection
        using (SqlConnection conn = new SqlConnection(connectionString))
        {
            //And get the command ready
            SqlCommand command = new SqlCommand(query, conn);
            //Open the connection.
            conn.Open();

            //Perform the query.
            using (SqlDataReader reader = command.ExecuteReader())
            {
                //If we got results...
                if (reader.HasRows)
                {
                    //Loop through them
                    while (reader.Read())
                    {

                        //Create a link
                        HyperLink link = new HyperLink();

                        link.Text = reader["Title"].ToString();
                        link.NavigateUrl = "http://www.yoursite.com/" +
                            reader["BlogId"].ToString();
                        link.CssClass = "ajaxSiteSearchLink";

                        //Add it to the PlaceHolder
                        plhResults.Controls.Add(link);
                    }
                }
                else //No results found
                {
                    //more code here
                }
            }
        }
    }
}

The HyperLink control represents a normal hyperlink. It contains data and has a URL to navigate to when clicked. This code creates a HyperLink control programmatically by creating an instance of the HyperLink class. The value of the Title database column is assigned to the Text, the NavigateUrl property is set to the URL to navigate to, and the CssClass property assumes the value of "ajaxSiteSearchLink". After building the HyperLink, it is then added to plhResults.

The final step in btnSearch_Click() is to tell the user when a search did not find a match; throwing a simple exception works just fine for this purpose due to the AJAX Extensions' handling of server-side errors:

protected void btnSearch_Click(object sender, EventArgs e)
{
    //Get the search string
    string searchString = txtSearchTerm.Text;

    //Check to see if anything was entered.
    //If not, tell the user to enter text.
    if (searchString == String.Empty)
    {
        throw new Exception("Please enter a search term.");
    }
    else
    {
        //Get our connection string.
        string connectionString =
ConfigurationManager.ConnectionStrings["SiteSearch"].ConnectionString;
        //Build the query.
        string query = String.Format("SELECT TOP 10 BlogId, " +
            "Title FROM BlogPosts WHERE Post LIKE '%{0}%' " +
            "OR Title LIKE '%{0}%' ORDER BY Date DESC", searchString);

        //Set up the database connection
        using (SqlConnection conn = new SqlConnection(connectionString))
        {
            //And get the command ready
            SqlCommand command = new SqlCommand(query, conn);
            //Open the connection.
            conn.Open();

            //Perform the query.
            using (SqlDataReader reader = command.ExecuteReader())
            {
                //If we got results...
                if (reader.HasRows)
                {
                    //Loop through them
                    while (reader.Read())
                    {
                        //Create a link
                        HyperLink link = new HyperLink();

                        link.Text = reader["Title"].ToString();
                        link.NavigateUrl = "http://www.yoursite.com/" +
                            reader["BlogId"].ToString();
                        link.CssClass = "ajaxSiteSearchLink";

                        //Add it to the PlaceHolder
                        plhResults.Controls.Add(link);
                    }
                }

else //No results found
                {

                    //Let the user know.
                    throw new Exception("No match could be found.");
                }
            }
        }
    }
}

This final line in this event handler throws a new generic exception, telling the user that no match could be found.

14.4.5. Clearing the Results

The results are cleared when the Clear button is clicked:

protected void btnClear_Click(object sender, EventArgs e)
{
    foreach (Control control in plhResults.Controls)
        plhResults.Controls.Remove(control);

    txtSearchTerm.Text = String.Empty;
}

Clearing the form involves two operations. The first removes the PlaceHolder's child controls (HyperLink controls). By using a foreach loop to loop through the PlaceHolder's controls, this code removes each control with the Controls.Remove() method. Once empty, the Text property of the txtSearchTerm TextBox is set to an empty string, clearing any search term that the user entered.

The TextBox's UpdatePanel is set to always update. If it were set to update conditionally, this code would not clear the text within the box.

14.4.6. Handling Errors

The btnSearch_Click() event handler throws two errors, so the AsyncPostBackError event for the ScriptManager must be handled.

protected void ScriptManager1_AsyncPostBackError(object sender,
                                                 AsyncPostBackErrorEventArgs e)
{
    ScriptManager1.AsyncPostBackErrorMessage = e.Exception.Message;
}

This code simply sets the AsyncPostBackErrorMessage property so that the user gets informational error messages.

14.4.7. Hooking Up the Events

The final step in this rewrite is hooking up the event handlers to the Button controls. For this example, the event handlers are added through the markup:

<div class="ajaxSiteSearchContainer">
    <form id="form1" class="ajaxSiteSearchForm" runat="server">
        <asp:ScriptManager ID="ScriptManager1" runat="server"
            OnAsyncPostBackError="ScriptManager1_AsyncPostBackError" />
        <asp:UpdatePanel ID="upTextBoxUpdate" RenderMode="Inline" runat="server">
            <ContentTemplate>
                <asp:Textbox CssClass="ajaxSiteSearchTextBox"
                            ID="txtSearchTerm" runat="server" />
            </ContentTemplate>
        </asp:UpdatePanel>

        <asp:Button CssClass="ajaxSiteSearchButton" ID="btnSearch" Text="Go"
            OnClick="btnSearch_Click" runat="server"  />
        <asp:Button CssClass="ajaxSiteSearchButton" ID="btnClear" Text="Clear"
            OnClick="btnClear_Click" runat="server"  />

        <div class="ajaxSiteSearchResultPane">
            <asp:UpdatePanel UpdateMode="Conditional" ID="upResultsUpdate"
                runat="server">
                <ContentTemplate>
                    <asp:PlaceHolder ID="plhResults" runat="server" />
                </ContentTemplate>
                <Triggers>
                   <asp:AsyncPostBackTrigger ControlID="btnSearch"
                       EventName="Click" />
                   <asp:AsyncPostBackTrigger ControlID="btnClear" EventName="Click"
/>
                </Triggers>
            </asp:UpdatePanel>
        </div>
    </form>
</div>

This new code simply adds the OnClick event handler to the Button controls. Now when they're clicked, the events will execute the correct code.

With this addition, the rewrite is complete. Open your web browser and point it to http://yourserver/ASPAjaxSiteSearch. You'll see Figure 14-4.

Figure 14.4. Figure 14-4

Now type "lo" in the search box and click the Go button. Figure 14-5 shows what you should see.

That's it! You've duplicated the AjaxSiteSearch widget from Chapter 12, and even expanded on it, while writing less code and without writing any JavaScript!

Figure 14.5. Figure 14-5

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset