The business tier consists of the SearchCatalog method, which calls the SearchCatalog stored procedure. This data feeds our older friend, the ProductsList.ascx Web User Control, which displays the search results.
Apart from a little bit of logic to handle splitting the search phrase into separate words (the presentation tier sends the whole phrase, but the data tier needs individual words) and to ensure we send a valid True/False value for the @AllWords parameter to the SearchCatalog stored procedure, there’s nothing fantastic about this new method.
Like always, you set up the stored procedure parameters, execute the command, and return the results. Add the Search method to your CatalogAccess class:
// Search the product catalog
public static DataTable Search(string searchString, string allWords, string pageNumber, out int howManyPages)
{
// get a configured DbCommand object
DbCommand comm = GenericDataAccess.CreateCommand();
// set the stored procedure name comm.CommandText = "SearchCatalog";
// create a new parameter
DbParameter param = comm.CreateParameter();
param.ParameterName = "@DescriptionLength";
param.Value = BalloonShopConfiguration.ProductDescriptionLength;
param.DbType = DbType.Int32;
comm.Parameters.Add(param);
// create a new parameter param = comm.CreateParameter();
param.ParameterName = "@AllWords";
param.Value = allWords.ToUpper() == "TRUE" ? "True" : "False";
param.DbType = DbType.Boolean;
comm.Parameters.Add(param);
// create a new parameter param = comm.CreateParameter();
param.ParameterName = "@PageNumber";
param.Value = pageNumber;
param.DbType = DbType.Int32;
comm.Parameters.Add(param);
// create a new parameter param = comm.CreateParameter();
param.ParameterName = "@ProductsPerPage";
param.Value = BalloonShopConfiguration.ProductsPerPage;
param.DbType = DbType.Int32;
comm.Parameters.Add(param);
// create a new parameter param = comm.CreateParameter();
param.ParameterName = "@HowManyResults";
param.Direction = ParameterDirection.Output;
param.DbType = DbType.Int32;
comm.Parameters.Add(param);
// define the maximum number of words int howManyWords = 5;
// transform search string into array of words
char[] wordSeparators = new char[] { ',', ';', '.', '!', '?', '-', ' ' };
string[] words = searchString.Split(wordSeparators, StringSplitOptions.RemoveEmptyEntries);
int index = 1;
// add the words as stored procedure parameters
for (int i = 0; i <= words.GetUpperBound(0) && index <= howManyWords; i++) // ignore short words
if (words[i].Length > 2) {
// create the @Word parameters param = comm.CreateParameter();
param.ParameterName = "@Word" + index.ToString();
param.Value = words[i];
param.DbType = DbType.String;
comm.Parameters.Add(param);
index++;
}
// execute the stored procedure and save the results in a DataTable DataTable table = GenericDataAccess.ExecuteSelectCommand(comm);
// calculate how many pages of products and set the out parameter int howManyProducts =
Int32.Parse(comm.Parameters["@HowManyResults"].Value.ToString());
howManyPages = (int)Math.Ceiling((double)howManyProducts / (double)BalloonShopConfiguration.ProductsPerPage);
// return the page of products return table;
}
Because the code is pretty clear, it’s not worth analyzing it again in detail. However, note the following aspects:
• To guard against bogus values, we make sure to set the @AllWords parameter strictly to True or False, using the allWords.ToUpper() == "TRUE" ? "True" : "False" construct.
• The words in the search phrase are split on the list of characters contained in the wordSeparators array.
• There’s a for loop to add the @Word parameters. Short words (fewer than three letters long) are considered noise words and are not used for the search. Feel free to change this rule to suit your particular solution.
• The words searched for are returned though an out parameter, so the presentation tier is able to tell the visitor which words were actually used for searching.
• The number of pages is given by dividing the number of products by the number of products per page.
■ Note The maximum number of allowed words and the list of characters used to split the search string are hard-coded. In case you think any of these could ever change, it’s strongly recommended to save their values in web.config. Also note that increasing the maximum number of allowed words implies updating the SearchCatalog stored procedure as well.
Let’s now create the presentation tier, where you’ll use all the logic implemented so far.
Implementing the Presentation Tier
Let’s see some colors now! The Search Catalog feature has two separate interface elements that you need to implement. The first one is the place where the visitor enters the search string, shown earlier in Figure 5-1.
This part of the UI will be implemented as a separate user control named SearchBox.ascx, which provides a text box and a check box for the visitor. The other part of the UI consists of the search results page (Search.aspx), which displays the products matching the search criteria (refer to Figure 5-2).
The Search Box consists of a text box, a button, and a check box. Many times, ASP.NET programmers have complained about TextBox and Button controls working well together, especially when you want the same functionality to happen both when the Enter key is pressed while editing the TextBox and when the Button is clicked. For example, the standard way to respond to the Enter key in the text box is handling its TextChanged event, but this event is also raised when the button is clicked (in which case, it’s called twice). Other problems involve circumstances when you have more buttons on the form, and you need to associate their actions with the visitor hitting Enter in different text boxes.
The solution presented in this book is a bit more involved than other possible solutions, but it’s very powerful because after it’s in place, you can easily reuse it in other parts of the site, as you’ll see when adding new functionality in later chapters. You’ll add a method called TieButton to the Utilities class that can be called from any presentation tier component (Web User Controls, Web Forms, and so on) and that associates a TextBox control with an existing Button control. TieButton ensures than when the visitor presses Enter while editing the text box, the Click event of the Button is raised. This way you won’t need to deal with the TextChanged event of the TextBox any more.
This way of doing things is not exactly the “ASP.NET way,” which by design encourages you to handle everything with server-side code, but in this case breaking the rules a little bit brings some long-term benefits, and you’ll probably come to use the TieButton method in your other web projects as well. However, because the code is beyond the scope of this book, we won’t analyze the inner workings of TieButton here.