ptg 714 CHAPTER 16 Using the QueryExtender Control And you might define a static method in the page (though we recommend explicitly defining the type name so you can avoid filling your code-behinds with additional logic) as follows: public static IQueryable<Product> FilterOutPastDueAccounts(IQueryable<BankAccount> query) { return from account in query where account.Status != AccountStatuses.PastDue select account; } The return value from this method is passed down the chain of filter expressions. One great advantage of putting code behind these expressions is that they can be easily reused across multiple pages. (This is another reason we recommended specifying the type name of the expression class explicitly rather than putting the expression method in the page’s code-behind.) Sorting with the OrderByExpression The OrderByExpression provides developers with a way of specifying a sort expression that can be applied to the query within the query extender control. The two properties of the OrderByExpression that control the sorting are the DataField and Direction properties. The DataField property contains the name of the column on which the sorting will take place, and the Direction property indicates which direction the sorting will be done. <asp:QueryExtender runat=”server” TargetControlID=”AccountsReceivableSource”> <asp:OrderByExpression DataField=”AmountDue” Direction=”Descending”/> </asp:QueryExtender> In the preceding code we sort the accounts receivable rows in descending order by the amount due. This should give us a nice list of the people who owe us money and where they live. This might not be sufficient. What if, for example, for each dollar amount, we want to sort the receivables by the age of the customer? This can enable us to see the oldest person that owes us each amount of money. We can use a ThenBy tag as a child of the OrderByExpression to accomplish this as shown below: <asp:QueryExtender runat=”server” TargetControlID=”AccountsReceivableSource”> <asp:OrderByExpression DataField=”AmountDue” Direction=”Descending”> <asp:ThenBy DataField=”CustomerAge” Direction=”Descending”/> </asp:QueryExtender> From the Library of Wow! eBook ptg 715 Querying Data with Declarative Syntax Querying with the PropertyExpression The PropertyExpression enables the developer to use the query extender control to filter the result set based on the values of specific columns. Remember that the property expres- sion works only with equality. For example, you cannot use a property expression to filter where the value contained in a column is less than or greater than some other value. The following example shows how to filter a data source of ransom demands in which the ransom value is selected from a drop-down box: <asp:DropDownList AutoPostBack=”true” ID=”RansomValues” runat=”server”> <asp:ListItem Value=”1000000”>1 Meeeeelion Dollars!</asp:ListItem> <asp:ListItem Value=”1”>1 dollar</asp:ListItem> </asp:DropDownList> <asp:QueryExtender runat=”server” TargetControlID=”RansomDataSource”> <asp:PropertyExpression> <asp:ControlParameter ControlID=”RansomValues” Name=”RansomValue”/> </asp:PropertyExpression> </asp:QueryExtender> There’s actually quite a bit of interesting functionality in the preceding code. The first thing you see is that we have a drop-down list containing a list of possible filter values for ransom demands. In this code sample, we hardcoded the values but those could easily be data bound. Next is the query extender, extending a data source called RansomDataSource. The query in that data source is filtered to contain only those rows whose RansomValue column is exactly equal to the currently selected value of the RansomValues drop-down list. This means that if the user were to select the first value in the drop-down list, the page automatically posts back, and the data source automatically filters itself based on the new ransom value. Control Parameters enable us to continue to use declarative query extension at the same time as pulling valuable information from other controls on the page rather than poten- tially complicated and messy code-behind classes. Querying with the RangeExpression If the filter you’re looking to apply can be expressed as a range expression, this control can do the trick. A range expression is an expression in which you test whether a value is less than or greater than a target value or falls within a target range. This is a versatile expression, as shown in the following sample that filters a data source for rows in which the transaction posting date occurred within the two date periods chosen by the user with two text boxes that have jQuery-based calendar pop-ups feeding them (jQuery code is not included here because it’s beyond the scope for this chapter): 16 From the Library of Wow! eBook ptg 716 CHAPTER 16 Using the QueryExtender Control <asp:QueryExtender runat=”server” TargetControlID=”AccountsDataSource”> <asp:RangeExpression DataField=”TransactionPostDate” MinType=”Inclusive” MaxType=”Inclusive”> <asp:ControlParameter ControlID=”FromDate” /> <asp:ControlParameter ControlID=”ToDate” /> </asp:RangeExpression> </asp:QueryExtender> Querying with the SearchExpression The search expression functionality of the query extender is one of the most powerful expressions available. It enables you to specify a field or list of fields and search those fields by doing a comparison against a search string. This search can be a “starts with”, “contains”, or “ends with” search. Gone are the days in which the developer has to hand- write these kinds of queries, which is typically an arduous, error-prone process. This expression requires you to specify the SearchType and DataFields properties, but it does the rest of the magic for you. Optionally, you can use the ComparsionType to control the case-sensitivity of the search (depending on whether the underlying data source you used supports this functionality). As with all these expressions, you can combine them with other expressions to create rich queries without having to drop into your code-behind to do manual sorting and filtering. The following sample illustrates how to use the search expression to provide a full- featured search page (although a real-world, production search page would probably have a better UI!) <form id=”searchForm” runat=”server”> Query: <asp:TextBox ID=”SearchQueryTextBox” runat=”server”/><br/> <asp:Button ID=”searchButton” runat=”server” Text=”Search” /> <asp:LinqDataSource ID=”ContactsDataSource” ContextTypeName=”MyDatabaseDataContext” TableName=”Contacts” runat=”server” /> <asp:QueryExtender runat=”server” TargetControlID=”ContactsDataSource”> <asp:SearchExpression SearchType=”Contains” DataFields=”FirstName,LastName,UserName”> <asp:ControlParameter ControlID=”SearchQueryTextBox” /> </asp:SearchExpression> </asp:QueryExtender> <asp:GridView ID=”ContactsGrid” runat=”server” DataSourceID=”ContactsDataSource”> </asp:GridView> </form> From the Library of Wow! eBook ptg 717 Building a Sample Page with the QueryExtender Control By now most of the markup in the preceding code sample should look familiar; however no code-behind is driving the actual querying of the database. The query extender is auto- matically going to modify the query (if there is anything in the search box) and filter the results every time a postback occurs. The button press causes a postback but no code in the code-behind needs to be executed because all the search logic has been rigged up declaratively in the .aspx page. Whether having this “querying as a side-effect” is a good thing is a debate that will proba- bly rage on for quite some time as many developers prefer to explicitly control when and how the query is performed, and obviously proponents of ASP.NET MVC Framework would have the query instigated in a controller action method rather than declaratively in the markup. Building a Sample Page with the QueryExtender Control Now that we’ve seen all the different options available to us when using the QueryExtender control, let’s put this all together with a real data source and some controls on the page and see how it all works as a unit. In this sample, we point a LINQ data source (a data source for LINQ to SQL contexts) at a context that contains tables for a video game catalog database. When the data source is set up, we just need to create an ASP.NET Web Form page called GameBrowser.aspx. This page needs to have a drop-down list that enables users to filter games by Genre and a text box that enables users to search both game title and game description. Take a look at the markup for the GameBrowser.aspx page: <form id=”form1” runat=”server”> <div> Genre: <asp:DropDownList runat=”server” ID=”GenreDropDownList” DataSourceID=”GenreSource” DataTextField=”Name” DataValueField=”ID” AutoPostBack=”true” /><br /> Game Search: <asp:TextBox ID=”GameSearchTextBox” runat=”server” /><br /> <asp:GridView runat=”server” ID=”GamesGrid” DataSourceID=”GamesSource” AutoGenerateColumns=”False” BackColor=”LightGoldenrodYellow” BorderColor=”Tan” BorderWidth=”1px” CellPadding=”2” ForeColor=”Black” GridLines=”None”> <AlternatingRowStyle BackColor=”PaleGoldenrod” /> <Columns> <asp:BoundField DataField=”ID” HeaderText=”ID” ReadOnly=”True” SortExpression=”ID” /> <asp:BoundField DataField=”Title” HeaderText=”Title” ➥ ReadOnly=”True” SortExpression=”Title” /> <asp:BoundField DataField=”Description” HeaderText=”Description” 16 From the Library of Wow! eBook ptg 718 CHAPTER 16 Using the QueryExtender Control ReadOnly=”True” SortExpression=”Description” /> <asp:BoundField DataField=”GenreID” HeaderText=”GenreID” ➥ ReadOnly=”True” SortExpression=”GenreID” /> <asp:BoundField DataField=”GenreName” HeaderText=”Genre Name” Read ➥ Only=”true” /> <asp:BoundField DataField=”ESRB” HeaderText=”ESRB” ReadOnly=”True” SortExpression=”ESRB” /> </Columns> <FooterStyle BackColor=”Tan” /> <HeaderStyle BackColor=”Tan” Font-Bold=”True” /> <PagerStyle BackColor=”PaleGoldenrod” ForeColor=”DarkSlateBlue” HorizontalAlign=”Center” /> <SelectedRowStyle BackColor=”DarkSlateBlue” ForeColor=”GhostWhite” /> <SortedAscendingCellStyle BackColor=”#FAFAE7” /> <SortedAscendingHeaderStyle BackColor=”#DAC09E” /> <SortedDescendingCellStyle BackColor=”#E1DB9C” /> <SortedDescendingHeaderStyle BackColor=”#C2A47B” /> </asp:GridView> <asp:LinqDataSource ID=”GamesSource” runat=”server” ContextTypeName=”QueryExtenderApplication.VideoGamesModelDataContext” TableName=”VideoGames” EntityTypeName=”” Select=”new (Genre.Name as GenreName, ID, Title, Description, GenreID, ➥ ESRB)” /> <asp:LinqDataSource ID=”GenreSource” runat=”server” ContextTypeName=”QueryExtenderApplication.VideoGamesModelDataContext” TableName=”Genres” EntityTypeName=”” /> <asp:QueryExtender runat=”server” TargetControlID=”GamesSource”> <asp:PropertyExpression> <asp:ControlParameter ControlID=”GenreDropDownList” Name=”GenreID” /> </asp:PropertyExpression> <asp:SearchExpression ComparisonType=”InvariantCultureIgnoreCase” DataFields=”Title,Description” SearchType=”Contains”> <asp:ControlParameter ControlID=”GameSearchTextBox” /> </asp:SearchExpression> </asp:QueryExtender> </div> </form> From the Library of Wow! eBook ptg 719 Building a Sample Page with the QueryExtender Control Aside from the few bits of styling information that came from choosing Autoformat on the GridView, the preceding code isn’t all that complex. The first thing we do is create a drop-down list bound to the list of genres in the video games catalog. Second, we create a text box that will eventually filter the list of games. After the GridView, there are two LINQ data sources. The first data source points to the Genre table in the video game LINQ to SQL model. The second points to the VideoGames table; you might notice that we’re actually including a foreign key property (Genre.Name) and adding that to the anonymous type returned by the query. Finally, we get to the query extender. The first part of the query extender is a PropertyExpression that we use to filter the GenreID property based on the currently selected value of the GenreDropDownList control. When you try this page (after creating a bogus video game catalog), you see that the form automatically reposts and requeries every time you select a new genre. Obviously you can change this behavior for a produc- tion application, but it helps drive home the point that all the QueryExtender functional- ity can be contained solely in the aspx markup. The second part of the query extender is a SearchExpression. Here we’re further filtering the result set by searching the Title and Description columns of the VideoGame table for a substring indicated by the current value of the GameSearchTextBox control. Figure 16.1 shows the output of the page after selecting a genre from the drop-down box. Figure 16.2 shows the output of the same page after supplying some text on which to filter the games list. Note that there’s no button on this page, just hitting enter within the text box will cause a re-query after a postback. 16 FIGURE 16.1 Filtering using the QueryExtender. From the Library of Wow! eBook ptg 720 CHAPTER 16 Using the QueryExtender Control Summary This chapter provided you with an overview of building data-driven pages in which the filtering and sorting logic was embedded directly in the page in a declarative fashion using the QueryExtender control and its associated expressions. By enabling you to define your queries and sort expressions declaratively in the markup, the QueryExtender also enables you to make those queries dynamic by pulling in values from other controls on the page at runtime. This makes building user-driven forms that perform searches, queries, and sorts incredibly easy. The QueryExtender control is just one more tool for the ASP.NET developer’s arsenal for rapidly building powerful, data-driven Web Forms. FIGURE 16.2 Filtering using the QueryExtender and a SearchExpression. From the Library of Wow! eBook ptg CHAPTER 17 Building Components IN THIS CHAPTER . Building Basic Components . Building Component Libraries . Architectural Considerations . Summary Components enable you to reuse application logic across multiple pages or even across multiple applications. For example, you can write a method named GetProducts() once and use the method in all the pages in your website. By taking advantage of components, you can make your applications easier to maintain and extend. For simple applications, you do not need to take advantage of components; however, as soon as your application contains more than a few pages, you’ll discover that you are repeating the same work over and over again. Whenever you discover that you need to write the same method more than once, you should immediately rip the method out of your page and add the method to a component. In this chapter, you learn how to build components in .NET Framework. First, you get an overview of writing compo- nents: You learn how to create simple components and use them in the pages in your application. In particular, you learn how to define component methods, properties, and constructors. You also learn how to take advantage of over- loading, inheritance, and interfaces. Next, you learn how to build component libraries that can be shared across multiple applications. You examine differ- ent methods of compiling a set of components into assem- blies. You also learn how you can add a component library to the Global Assembly Cache. Finally, we discuss architectural issues involved in using components. The final section of this chapter shows you how to build a simple three-tiered application that is divided into distinct User Interface, Business Logic, and Data Access layers. From the Library of Wow! eBook ptg 722 CHAPTER 17 Building Components NOTE Let’s clarify the terminology. In this book, I use the word component as a synonym for the word class. Furthermore, by the word object, I mean an instance of a class. I am ignoring a special meaning for the word component in .NET Framework. Technically, a component is a clas s that implements the System.ComponentModel.IComponent interface. I am ignoring this special meaning of the word component in favor of the common language use of the word. Building Basic Components Let’s start by building a super simple component. The HelloWorld component is contained in Listing 17.1. LISTING 17.1 HelloWorld.cs public class HelloWorld { public string SayMessage() { return “Hello World!”; } } VISUAL WEB DEVELOPER NOTE When using Visual Web Developer, you create a component by selecting Website, Add New Item, and then selecting the Class item (see Figure 17.1). The first time you add a component to a project, Visual Web Developer prompts you to create a new folder named App_Code. You want your new component to be added to this folder. The HelloWorld component consists of a single method named SayMessage() that returns the string Hello World!. Make sure that you save the HelloWorld.cs file to your application’s App_Code folder. If you don’t save the component to this folder, you can’t use the component in your pages. Next, you need to create a page that uses the new component. This page is contained in Listing 17.2. From the Library of Wow! eBook ptg 723 Building Basic Components FIGURE 17.1 Creating a new component with Visual Web Developer. LISTING 17.2 ShowHelloWorld.aspx <%@ Page Language=”C#” %> <!DOCTYPE html PUBLIC “-//W3C//DTD XHTML 1.1//EN” “http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd”> <script runat=”server”> void Page_Load() { HelloWorld objHelloWorld = new HelloWorld(); lblMessage.Text = objHelloWorld.SayMessage(); } </script> <html xmlns=”http://www.w3.org/1999/xhtml” > <head id=”Head1” runat=”server”> <title>Show Hello World</title> </head> <body> <form id=”form1” runat=”server”> <div> <asp:Label id=”lblMessage” Runat=”server” /> 17 From the Library of Wow! eBook . Value=”1”>1 dollar< /asp: ListItem> < /asp: DropDownList> < ;asp: QueryExtender runat=”server” TargetControlID=”RansomDataSource”> < ;asp: PropertyExpression> < ;asp: ControlParameter ControlID=”RansomValues”. /> < /asp: PropertyExpression> < ;asp: SearchExpression ComparisonType=”InvariantCultureIgnoreCase” DataFields=”Title,Description” SearchType=”Contains”> < ;asp: ControlParameter ControlID=”GameSearchTextBox”. TableName=”Genres” EntityTypeName=”” /> < ;asp: QueryExtender runat=”server” TargetControlID=”GamesSource”> < ;asp: PropertyExpression> < ;asp: ControlParameter ControlID=”GenreDropDownList” Name=”GenreID”