Building Web Sites with ASP.NET - Part 1
 
Published: 15 Oct 2008
Abstract
In this first part of the series, Brian delves into setting up an ASP.NET web application that makes use of object-oriented design patterns while making use of the latest and greatest technologies as they make sense. He begins with an overview of the project creation using Visual Studio 2008 including the coverage of the modal popups feature. Brian also examines the usage of themes and the implementation of searching with the help of screenshots and related source code.
by Brian Mains
Feedback
Average Rating: This article has not yet been rated.
Views (Total / Last 10 Days): 29213/ 41

Introduction

ASP.NET AJAX applications are very dynamic and rich, creating a new generation of web applications. This article illustrates some of the tips and tricks to creating rich features in an application.

Setting up the Project

The first step in setting up the project is to add a new project, which will be the web site (notice I started out with a blank solution). This article uses the web application project template; the web site template can be used by selecting Add > New Web Site option instead. I tend to prefer the web application project model.

Figure 1

The next step is to setup the web site by giving it the name of WebSiteStarterKit. Notice I am using the ASP.NET Web Application option.

Figure 2

Since this is a .NET 3.5 Framework project, the project template sets up some of the AJAX features and .NET 3.5 components. Take a look at the web.config file to see these new settings if you are unfamiliar with them.

Let us start by adding an AJAX master page. To create the master page, select the AJAX master page option, and give it the name of Site.Master.

Figure 3

This template definition is a typical master page format, plus a reference to the ScriptManager.  Take a look at the following script definition.

Listing 1

<%@ Master Language="C#" AutoEventWireup="true" CodeBehind="Site.master.cs" 
Inherits="WebSiteStarterKit.Site" %>
 
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>Untitled Page</title>
    <asp:ContentPlaceHolder ID="headPlaceholder" runat="server">
    </asp:ContentPlaceHolder>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <asp:ScriptManager ID="ScriptManager1" runat="server" />
        <asp:ContentPlaceHolder ID="bodyPlaceholder" runat="server">
        
        </asp:ContentPlaceHolder>
    </div>
    </form>
</body>
</html>

This is the base template of the initial master page; however, to incorporate a wide array of features, I may create multiple master pages. Multiple master pages create a personalized approach, and is expandable.

Alright, it is time to build in some features.

Modal Popups

We will start with the first feature of modal popups through the ModalPopupExtender. This extender pops up a panel whenever the target control is clicked. The target is usually a button, but in this case the target is the login status control (which under the hood is a button). When the login status is clicked, it pops up a panel to login (for logging in only).

Listing 2

<asp:LoginStatus ID="lgsLogin" runat="server" CssClass="SiteLayoutMenuBarItem" />
<asp:Panel ID="pnlLogin" runat="server" CssClass="ModalPopupPanelBackground">
      Please login to the application.<br /><br />                            
      <asp:Login ID="lgnLogin" runat="server" />
      <br />      <hr />      <br />
      Use the form below to email your password to you.<br /><br />
      <asp:PasswordRecovery ID="prLogin" runat="server" />
      <asp:Button ID="btnCancel" runat="server" Text="Cancel" />
</asp:Panel>
<ajax:ModalPopupExtender ID="mpeLogin" runat="server" TargetControlID="lgsLogin" 
  PopupControlID="pnlLogin" CancelControlID="btnCancel" 
  BackgroundCssClass="ModalPopupBackground" />

The ModalPopupExtender targets the LoginStatus control, the first element, through its TargetControlID. It knows to popup a Panel, the second element, because the PopupControlID is set to its ID. Now, instead of the default click action, the modal popup appears when the LoginStatus is clicked, and allows the user to login through a modal interface.

The modal interface allows the user to login or recover their password. The action can be cancelled using the cancel button. The ModalPopupExtender can target an OK and Cancel button for the modal window, which will close the window when clicked.

Let us take a look at what this looks like in the application. When the button is clicked, the following screen appears.

Figure 4

Notice the gray background; this background covers the entire screen, preventing the user from making any modifications to the background information. The user has to focus his or her attention solely on the login/password recovery process at hand.

Selecting a Theme

The next process discussed is selecting a theme for the site. Some web sites allow you to choose the theme of the site, which changes the color style of the site to a different color. This is a nice feature to have for a site, so we will implement that here.

The .NET 2.0 Framework incorporated the idea of themes into a site, which allows the site to be personalized with both client-side CSS and server-side Skins, a new way to apply styles using the .NET server control conventions.  If you have not seen a server-side skin, let us take a look at the following skin for the Login control.

Listing 3

<asp:Login runat="server" BackColor="#EFF3FB" BorderColor="#B5C7DE" 
  BorderPadding="4" BorderStyle="Solid" BorderWidth="1px" ForeColor="#333333">
<TextBoxStyle />
<LoginButtonStyle BackColor="White" BorderColor="#507CD1" BorderStyle="Solid" 
  BorderWidth="1px" ForeColor="#284E98" />
<InstructionTextStyle Font-Italic="True" ForeColor="Black" />
<TitleTextStyle BackColor="#507CD1" Font-Bold="True" ForeColor="White" />
</asp:Login>

A skin can assign a value to any property that has the Themeable attribute set, which is not always known. The way to know whether this works or not, is to set the value in the skin and run the application. If you cannot assign a property value using the skin, the compiler will let you know.

A personalized skin for a specific situation can be assigned by adding the SkinID attribute. If a skin has a SkinID specified, any control that has a matching SkinID (2.0 also added the SkinID property at the control level) will apply that skin. This skin overrides the default skin applied.

CSS styles work the same way, except they set client-side attribute values instead. For instance, the following defines two CSS classes.

Listing 4

hr
{
      border:solid 1px navy;
      width:1px;
}
.ModalPopupBackground
{
      background-color: #C0C0C0;
}
.ModalPopupPanelBackground
{
      background-color: #FFFFCC;
      color: #000080;
      border: solid 1px #000080;
}

The period denotes a CSS class that is applied through the HTML class property, whereas the first style works with the HR tag simply by matching the name.

All of these styles make up a theme, which this example will allow the user to change.  The interface for changing the themes appears below.

Listing 5

<asp:DropDownList ID="ddlTheme" runat="server" 
  CssClass="SiteLayoutMenuBarDropDown"
  DataTextField="FriendlyName" DataValueField="Name" 
  onchange="themeDropDown_change"></asp:DropDownList>
<asp:LinkButton ID="lnkThemeChange" runat="server" CausesValidation="false"
  CssClass="SiteLayoutMenuBarItem">Change Theme</asp:LinkButton>

Clicking on the "change theme" button, this posts back to the server to show the new theme.  There is a two-step process for changing the theme of a site dynamically. The first challenge is accessing the value of the selected theme and storing it for the post back. The second option is loading the stored value and changing the theme.

The challenge with this is that changing themes at runtime has to happen during PreInit, which can be a challenge. Before getting to this, let us look at the approach used.

Searching

Most applications have some sort of search capability built into a site. This makes it convenient to quickly access related content that is being sought for. Sometimes it can be helpful to see what has been searched for in the past, along with how many instances of that search item have been found.

The following markup exists in the master page, a perfect place to put a search textbox.

Listing 6

<asp:TextBox ID="txtSearch" runat="server" />
<ajax:TextBoxWatermarkExtender ID="extSearch" runat="server" 
      TargetControlID="txtSearch"
      WatermarkText="Type to Search" />
<ajax:AutoCompleteExtender ID="extSearch2" runat="server" 
      TargetControlID="txtSearch"
      ServicePath="~/Services/SearchService.asmx" 
      ServiceMethod="GetTopSearchPhrases"
      MinimumPrefixLength="1" CompletionListItemCssClass="AutoCompleteItemStyle" 
      CompletionListHighlightedItemCssClass="AutoCompleteSelectedItemStyle"
      OnClientItemSelected="autoComplete_itemSelected" />
<asp:LinkButton ID="lnkSearch" runat="server" CssClass="SiteLayoutMenuBarItem"
      OnClick="lnkSearch_Click">Search</asp:LinkButton>

There is quite the number of .NET controls for implementing a search. Outside of the textbox that retains search criteria and a linkbutton that triggers the search, the two extenders help add on additional functionality without requiring a custom control. The TextBoxWatermarkExtender helps by providing a message to the user identifying the purpose of the search textbox. The second extender, the AutoCompleteExtender, is a powerful extender that queries the database, looking for existing search results and returning the list.

The AutoCompleteExtender works by using a web service and web method to get the items that match the text that the user has entered. The web service has to conform to a specific signature for this to work, which I will discuss in a moment.

As the user clicks the submit button, whatever is searched for is logged to the database. There are several schemes that can be implemented to track what the user searches for, and create a popularity ranking. The first question you have to ask is shall whatever the user enters be logged or only searches that have results? Should low to high volume results be differentiated?

In this example, whatever the user enters is stored in the database, but more ideally any search text that has actual results should be stored. This could be done whenever loading the search page or search results.

Another question is what is the search querying? Is the search using a full-text index in the database, index server, or some other mechanism? This is important too, though I do not have a solution implemented in this example; I am only focused on storing the user's query.

Let us start with the web service definition. As I mentioned before, a web service used by the AutoCompleteExtender needs to conform to a specific signature, shown below.

Listing 7

[WebMethod]
public string[] GetTopSearchPhrases(string prefixText, int count)
{
  SearchManager manager = SearchManager.GetManager();
  SearchPhraseCollection phraseCollection =
  manager.GetTopSearchPhrases(prefixText, count);
  return (from p in phraseCollection
  select string.Format("{0} ({1} occurrence(s))", p.Phrase,
  p.ResultCount.ToString())).ToArray();
}

The method takes two to three parameters, depending on the configuration. In this example, the method takes the prefixText, which is the characters the user has entered, and the count, the total number of items to limit the search by. The latter value is specified in the body of the AutoCompleteExtender; it is configurable by changing the CompletionSetCount property value.

If the UseContextKey property of that extender is set to true, a third parameter, contextKey, can be added as well. Notice that an array of strings is returned to the caller. This is also required.  Notice the specialized output, which will include items in the format of "Toasters (12 occurrence(s))."

As the user enters keys, the user is prompted with a selection of items as shown in the screenshot below.

Figure 5

These entries can be selected using the mouse, or by clicking the up and down arrow. Selecting an entry places the item in the textbox. These entries are stored in the database using the following code in the search button click event (next to the textbox, but not shown above).

Listing 8

protected void lnkSearch_Click(object sender, EventArgs e)
{
  if (string.IsNullOrEmpty(this.txtSearch.Text))
  return;
  string text = this.txtSearch.Text.Trim();
  if (string.IsNullOrEmpty(text))
  return;
  //Updates the search phrase count.
  SearchManager manager = SearchManager.GetManager();
  manager.UpdateSearchPhraseCount(text);
}

The SearchManager component makes a call to the data layer to perform the update. If the searched text already exists, then the number of occurrences is incremented by one; otherwise, a new entry is created. There is only one problem not yet discussed. Upon selecting an item in the auto complete popup, the text "(X occurrences)" would also be copied to the textbox. This is not very useful, and needs to be stripped out.

The solution to this is to attach to the itemSelected client event. Notice in the AutoCompleteExtender markup above, the OnClientItemSelected references the name of an event handler. This event handler performs the work of stripping out the "(X occurrences)" text from the selected entry. It does this with the following JavaScript.

Listing 9

function autoComplete_itemSelected(sender, e)
{
      var text = e.get_text();
      if (text.indexOf('(') > 0)
      {
            text = text.substring(0, text.indexOf('('));
            if (text != null && text.length > 0)
                  text = text.trimEnd();
      }
      var targetElement = sender.get_element();
      targetElement.value = text;
}

The event argument in this event is AutoCompleteItemEventArgs, which has an item, text, and value property. I am using the text property getter, and parsing it to look for the first parenthesis. If found, it is stripped out and assigned to the target element, and is accessible using the element property getter. Remember that extenders target another property and do not emit its own interface, so an extender's element property references the extended control.

The issue that most people will have with the solution above is that there is not any intellisense to find all this out, and there is not any MSDN documentation. I had to manually dig through the AJAX control toolkit source code to find all of this information.  I would highly recommend looking at the source code whenever using these extenders.

Conclusion

With ASP.NET AJAX, it is easy to include great features into an application related to the membership framework, searching, themeing, and other areas of the application as well. I plan to continue the series to show other facets of web site development.



User Comments

No comments posted yet.

Product Spotlight
Product Spotlight 





Community Advice: ASP | SQL | XML | Regular Expressions | Windows


©Copyright 1998-2024 ASPAlliance.com  |  Page Processed at 2024-04-24 4:07:19 PM  AspAlliance Recent Articles RSS Feed
About ASPAlliance | Newsgroups | Advertise | Authors | Email Lists | Feedback | Link To Us | Privacy | Search