On the first query to Live Search, the Search control will set up the Result control’s DataSource property with an instance of SearchResponse and call its DataBind method to have it bind
Trang 1Wrapping the Web Service Proxy in a Utility Method
To make it easier to work with the web service proxy, we wrap the creation and invocation
process inside a utility class that abstracts all the details of communicating with the Live Search
web service, as shown in Listing 12-3 This class also hides the work necessary to grab
configu-ration information from the custom configuconfigu-ration section we created earlier in this chapter
Listing 12-3 The SearchUtility.cs Class File
/// Static method for searching Live Search that wraps web service
proxy code for easy invocation
/// </summary>
/// <param name="query">Query to Live Search search web service</param>
/// <param name="sourceRequests">Collection of search settings</param>
/// <returns></returns>
public static LiveSearchService.SearchResponse SearchLiveSearchService(
string query, SourceRequest[] sourceRequests)
{
string LiveSearchLicenseKey = "";
string LiveSearchWebServiceUrl = "";
// get <liveSearchControl> config section from web.config
// for search settings
Trang 2// if control is instantiated at runtime config section should be present else if (HttpContext.Current != null)
{ throw new Exception(
"ControlsBook2Lib.LiveSearchControls.SearchUtility cannot find <LiveSearchControl> configuration section.");
} EndpointAddress liveSearchAddress = new EndpointAddress(LiveSearchWebServiceUrl);
BasicHttpBinding binding = new BasicHttpBinding();
ChannelFactory<MSNSearchPortType> channelFactory = new ChannelFactory<MSNSearchPortType>(binding, liveSearchAddress);
MSNSearchPortType searchService = channelFactory.CreateChannel();
SearchRequest searchRequest = new SearchRequest();
//Required parameters on SearchRequest searchRequest.Query = query;
searchRequest.AppID = LiveSearchLicenseKey;
searchRequest.CultureInfo = Thread.CurrentThread.CurrentUICulture.Name;
//Optional parameters for SearchRequest
if (sourceRequests != null) searchRequest.Requests = sourceRequests;
//Set mark query word Non-printable character added to highlight query terms //Set DisableHostCollapsing to return all results
searchRequest.Flags = SearchFlags.DisableHostCollapsing | SearchFlags.MarkQueryWords;
//Conduct Search SearchResponse searchResponse = searchService.Search(searchRequest);
return searchResponse;
} }}The SearchUtility class provides a parameter list to its single static SearchLiveSearchService method that accepts the search query string entered by the user and an array of SourceRequest objects This allows the custom server controls to customize what type of search is performed
by setting properties on the SourceRequest objects Consult Tables 12-1 through 12-4 for details
on what settings are available Now that we have covered how to work with the Live Search web service and the configuration architecture, we can focus on the custom server controls
Trang 3Designing the Control Architecture
At this point, you have an understanding of how to access the Live Search web service, and you
have some code to invoke it to return a set of search results In the next phase of this chapter,
you will learn how to display and interact with results from the Live Search web service
The result set returned by the Live Search web service does not have the tabular structure that traditional data-bound controls such as the Repeater control or the DataGrid control expect
The top-level Live SearchResponse class contains an array of SourceResponse objects that
repre-sents multiple search requests A SourceResponse object contains the overall status information
about the search result, which would more likely be used as a header format The SourceResponse
Results property contains an array of Result instances with the URL data for display in a
repeating item format The control we need to build has to work with the data on these two
separate levels to display it appropriately We achieve this by having templates that bind to
different portions of the data source We discussed how to create templates in Chapter 6
Another major consideration is how to abstract communications with Live Search so that
a developer can quickly add search capabilities to his or her application To provide this ease
of use, we encapsulate the Live Search web service searching inside of our control’s code base
We provide a public data-binding method to load up the control UI from the result set, but the
means to do it are abstracted away from the developer All a developer needs to do is customize
the UI and let the control do the heavy lifting of communicating with Live Search and paging
the result set
The first major architectural decision is to factor out the responsibilities of the control library
Instead of one supercontrol, we factor the functionality into three major controls: Search, Result,
and Pager We also have the ResultItem class, which contains the output templates as a utility
control in support of the Result server control The diagram in Figure 12-5 shows the
break-down of responsibilities
The Search control has the primary responsibility of gathering input from the user and setting up the Result control with the first page of results in a new search We want to separate
Search from Result to allow flexible placement of the Search control’s text boxes The text boxes
can be deployed in separate locations on a web form so as not to constrain the web application
developer from a UI perspective
The Result control handles the display of search results returned by the Live Search web service On the first query to Live Search, the Search control will set up the Result control’s
DataSource property with an instance of SearchResponse and call its DataBind method to have it
bind its templates to the result set This mimics the behavior of data-bound controls discussed
in Chapter 7
The Pager control is the third main control in our control library and is embedded as a child control of the Result control If paging is enabled, the Result control passes the Pager
control the result set so that it calculates the starting index offsets based on page size and
renders page links
Figure 12-6 shows the action that occurs with the Search, Result, and Pager controls on an initial search The end result is a rendered page with embedded links that lets the page post
back to itself to change the view of the search results
Trang 4Figure 12-5 The architecture of the Live Search controls
Figure 12-6 Controls in action on an initial search
5 Display data for paging
6 Display pages as command hyperlinks
1 2 3
PagerResult
Trang 5Interacting with the paging features of the Result control is the next bit of functionality we discuss When the links rendered by the Pager control are clicked, the control generates a server-
side event This event is mapped to the Result control, which then handles the process of going
back to the Live Search web service and getting the desired page in the original result set It sets
its own DataSource property and calls DataBind on itself This, in turn, starts the original binding
process to render the new result set Figure 12-7 shows the process graphically when the paging
functionality of our control is exercised
Figure 12-7 Controls in action after the paging link is clicked
Now that we have covered the overall design, we can move on to a more detailed analysis
of the source code in each control, starting in the next section with the Search server control
The Search Control
The Search control takes the input from the user to perform the search query To accomplish
this, we derive the control from the CompositeControl class The Query property exposes the
query string used to search the Live Search web service and is automatically set by the TextBox
control, which is the primary input control for the Search control The Search control does not
expose a starting index property, as it assumes it will be on a one-based scale when it executes
the query RedirectToLiveSearch is a special property that provides the Search control the
capability to ignore the Live Search web service and redirect the web form to the Live Search
web site as if the user had typed in a query at the Live Search site directly
The actual UI for the Search control is built in the composite control fashion of adding child controls from within the following CreateChildControls method The first control added
to the collection is a HyperLink to provide a clickable link back to Live Search as well Note that
the image is the official image made available by the Live Search service The searchTextbox
7 Display pages as hyperlink commands
Pager Result
Trang 6control is a TextBox control that grabs the input from the user The searchButton control is a Button control that handles posting the page contents from the client back to the web server Several LiteralControl instances are also added to the Controls collection to fill in the HTML spacing between the controls and provide breaks.
protected override void CreateChildControls(){
liveSearchLinkImage = new HyperLink();
Handling the Search
The top of Search.HandleSearch has code that checks an internal Boolean variable named searchHandled to make sure that if both events fire on the same postback, we don’t get dupli-cate searches occurring on the same query value unnecessarily, as shown here:
Trang 7// check to see if search was handled on this postback
// (this prevents TextChanged and ButtonClicked from
// requesting the same query twice on the Live Search web service)
of the Search control is used to do a dynamic lookup of the correct Result control via the Page
FindControl method Since FindControl is not recursive, we look for the Result control on the
Page as well as at the same nesting level, which is the approach taken by the Net Framework
data-bound control’s DataSourceID property
We also use this control reference to infer the correct value for the PageSize along with the Query property value
if (resControl == null)
resControl = (Result)this.NamingContainer.FindControl(ResultControl);
if (resControl == null)
throw new Exception("Either a Result control is not set on the " +
"Search Control or the Result control is not located on the " +
"Page or at the same nesting level as the Search control.");
SourceRequest[] sourceRequests = new SourceRequest[1];
sourceRequests[0] = new SourceRequest();
sourceRequests[0].Count = resControl.PageSize;
After getting the result data from the web service, we raise an event to any interested parties
The type of this event is named LiveSearchSearched This allows someone to use the Search
control as a data generator and build his or her own custom UI from the result sets We follow
the design pattern for invoking this event through a protected method with On as the prefix to
the search name, OnLiveSearchSearched, as shown here:
OnLiveSearchSearched(new
LiveSearchSearchedEventArgs(searchResponse));
The LiveSearchSearchedEventArgs class wraps the results of a Live Search web service query
We use that event argument’s definition to create a LiveSearchSearched event handler If you go
back to the Search control source code, you can see the code that exposes the LiveSearchSearched
Trang 8event with this event definition We use the generic EventHandler<T> class to help reduce memory footprint.
After the event is raised so that subscribers receive the Live Search web service search results,
we continue processing in the Search.HandleSearch method to bind data to the Result control: resControl.DataSource = searchResponse;
resControl.DataBind();
We set its DataSource property and call DataBind to have it fill its template structure with HTML that reflects the data of our web service query The final step in the HandleSearch method sets the searchHandled Boolean variable to ensure the control does not fire two Live Search searches if both the TextBox TextChanged and the Button Click events fire on the same postback.Listing 12-4 shows the source code for the Search control
Listing 12-4 The Search.cs Class File
#if LICENSED RsaLicenseData(
"55489e7a-bff5-4b3c-8f21-c43fad861dfa", "<RSAKeyValue><Modulus>mWpgckAepJAp4aU0AvEcGg3TdO+0VXws9LjiSCLpy7aQKD5V7uj49Exh1RtcB6TcuXxm0R6dw75VmKwyoGbvYT6btOIw
QgqbLhci5LjWmWUPEdBRiYsOLD0h2POXs9xTvp4IDTKXYoP8GPDRKzklJuuxCbbUcooESQoYHp9ppbE=</Modulus><Exponent>AQAB</Exponent>
</RSAKeyValue>"
), LicenseProvider(typeof(RsaLicenseProvider)),
#endif DefaultEvent("LiveSearchSearched"),Designer(typeof(SearchDesigner))]
public class Search : CompositeControl {
private const string LiveSearchWebPageUrl = "http://www.live.com";
Trang 9private const string LiveSearchWebSearchUrl =
private const int SearchTextBoxWidth = 200;
private const bool DefaultFilteringValue = false;
private const bool DefaultRedirectToLiveSearchValue = false;
private bool searchHandled;
private HyperLink liveSearchLinkImage;
private TextBox searchTextBox;
private Button searchButton;
Trang 10if (disposing) {
//Dispose of additional unmanaged resources here
if (license != null) license.Dispose();
base.Dispose();
} license = null;
_disposed = true;
} }
#endif /// <summary>
/// LiveSearchControls Result control to bind search results to for display /// </summary>
[DescriptionAttribute("Result control to bind search results to for display."), CategoryAttribute("Search")]
virtual public string ResultControl {
get { object control = ViewState["ResultControl"];
if (control == null) return "";
else return (string)control;
} set { ViewState["ResultControl"] = value;
} } /// <summary>
/// Search query string /// </summary>
[DescriptionAttribute("Search query string."), CategoryAttribute("Search")]
Trang 11virtual public string Query
/// <param name="s">Search button</param>
/// <param name="e">Event arguments</param>
protected void SearchButtonClick(object source, EventArgs e)
{
HandleSearch();
}
Trang 12private void HandleSearch() {
// check to see if search was handled on this postback // (this prevents TextChanged and ButtonClicked from // requesting the same query twice on the Live Search web service)
if (searchHandled == true) return;
// check for redirect of query processing to Live Search web site
if (RedirectToLiveSearch == true) {
this.Page.Response.Redirect(
LiveSearchWebSearchUrl + "?q=" + HttpContext.Current.Server.UrlEncode(Query), true);
}
if (ResultControl.Length != 0) {
// lookup the Result control we are linked to // and get the PageSize property value Result resControl = (Result)Page.FindControl(ResultControl);
if (resControl == null) resControl = (Result)this.NamingContainer.FindControl(ResultControl);
if (resControl == null) throw new ArgumentException("Either a Result control is not set on the " + "Search Control or the Result control is not located on the " + "Page or at the same nesting level as the Search control.");
SourceRequest[] sourceRequests = new SourceRequest[1];
sourceRequests[0] = new SourceRequest();
Trang 13// databind search results with the Result control
// we are linked with
/// Protected method for invoking LiveSearchSearched event
/// from within Result control
/// </summary>
/// <param name="lse">Event arguments including search results</param>
protected virtual void OnLiveSearchSearched(LiveSearchSearchedEventArgs e)
Trang 14/// Overridden to ensure Controls collection is created before external access /// </summary>
public override ControlCollection Controls {
get { EnsureChildControls();
return base.Controls;
} } }}Now that we have covered the search functionality, in the next section, we discuss how the returned results are processed in the Result control
The Result Control
The Result control is the most complex control of the Live Search controls library It is a templated, data-bound control that has the capability to page itself as well as access the web service to update the page range The Result server control takes its cue from the Repeater control we developed in Chapter 7 It provides a robust set of templates: HeaderTemplate, StatusTemplate, ItemTemplate, AlternatingItemTemplate, SeparatorTemplate, and FooterTemplate Each template also has a like-named Style object to modify the HTML that is rendered for style content: HeaderStyle, StatusStyle, ItemStyle, AlternatingItemStyle, SeparatorStyle, and FooterStyle The embedded Pager control has its style properties exposed by a Result class property named PagerStyle
Each template is pushed into an instance of the ResultItem control This is the primary child control of Result, and it provides the means for achieving access to search results from
a template data-binding expression As we mentioned previously, Result offloads most of the paging work to a control class named Pager, which handles offset and range calculations We
Trang 15stuff the Pager control inside a ResultItem, so that it can page content Figure 12-8 shows the
structural architecture of the Result control, including the portion handed off to the Pager control
Figure 12-8 The ResultItem structure inside the Result control
Notice the square boxes around the search terms “Live”, “Search”, and “Development” in the returned search results in Figure 12-8 The square boxes are nonprintable characters, so
that the developer can highlight search terms in the results if desired In the next section, we
discuss the details behind the ResultItem control class including how to fine-tune the search
results such as adding the ability to highlight search terms
The ResultItem Control
The ResultItem class takes on a structure that is common to containers used as data-bound
templates It has the well-known DataItem property, as well as ItemIndex and ItemType properties to
store the index it occupies in the collection of ResultItem controls aggregated by its parent Result
control The ResultItemType enumeration matches up its usage with the templates and styles from
the Result class as well
Inside this file, we also have a ResultItemEventHandler signature of ResultItem events
These provide interested clients with the capability to receive the creation (ItemCreated) and
Pager
Result ResultItem
Trang 16data-binding events (ItemDataBound) events of the parent Result control class Listing 12-5 presents the full text listing for the ResultItem control.
Listing 12-5 The ResultItem.cs Class File
using System;
using System.Web.UI.WebControls;
namespace ControlsBook2Lib.CH12.LiveSearchControls{
/// Represents status section below header /// </summary>
Status, /// <summary>
/// Represents search result item output /// </summary>
Item, /// <summary>
/// Represents search result alternating item output /// </summary>
AlternatingItem, /// <summary>
/// Represents separation between search result item or alternating item output /// </summary>
Separator, /// <summary>
/// Represents paging area below search result items /// </summary>
Pager,
Trang 17private object dataItem;
private ResultItemType itemType;
private int itemIndex;
/// Type of template the ResultItem control represents</param>
/// <param name="dataItem">Data from search query</param>
public ResultItem(int index, ResultItemType type, object dataItem)
Trang 18} } /// <summary>
/// Type of template the ResultItem control represents /// </summary>
public ResultItemType ItemType {
get { return itemType;
} } } /// <summary>
/// Specialized EventArgs which contains a ResultItem instance /// </summary>
public class ResultItemEventArgs : EventArgs {
private ResultItem item;
/// <summary>
/// Default constructor for ResultItemEventArgs /// </summary>
/// <param name="item">ResultItem control instance</param>
public ResultItemEventArgs(ResultItem item) {
this.item = item;
} /// <summary>
/// ResultItem control instance /// </summary>
public ResultItem Item {
Trang 19template in use The data-binding expressions reach into a different data objects when they
reference the Container.DataItem property depending on which ResultItem is referenced
The StatusTemplate is bound to the top-level LiveSearchSearchResult class through the DataItem
property The ItemTemplate and AlternatingItemTemplate are alternately bound to each
ResultElement class that makes up the search results HeaderTemplate, FooterTemplate, and
SeparatorTemplate are not bound to any data source and have a null DataItem value
Building the Result Control
To provide a pleasing UI experience out of the box and let the control render something when
it is blank or when it is data bound, we have three primary modes that the Result control
oper-ates in: blank, data binding, and postback The blank mode is used for displaying a UI even
when the user fails to link the control to a data source Data-binding mode is used when a data
source is provided and the user explicitly calls the DataBind method of the control Postback is
the mode the control takes on when it is sent back to the server from a postback event and the
control hydrates its structure from ViewState
The Blank Scenario
The default action of the Result control if you put it on a web form and leave it alone is triggered by
code in its override of the following RenderContents method If a Boolean named searchConducted
is not set, it fires off a call to Result control’s CreateBlankControlHierarchy method:
protected override void RenderContents(HtmlTextWriter writer)
{
// if no search, create a hierarchy with header and
// footer templates only
Trang 20After the call to the CreateBlankControlHierarchy method, the control next calls PrepareControlHierarchy to ensure all styles are applied to any user-provided templates Last, the control calls the base class method of RenderContents to do its work of iterating through the child controls and rendering them.
If you look at CreateBlankControlHierarchy, you see that it looks for the HeaderTemplate and FooterTemplate templates and creates a ResultItem control to wrap them using the CreateResultItem helper method We examine CreateResultItem in just a bit, but here is CreateBlankControlHierarchy:
private void CreateBlankControlHierarchy(){
if (HeaderTemplate != null) {
ResultItem headerItem = CreateResultItem(-1, ResultItemType.Header, false, null);
items.Add(headerItem);
}
if (FooterTemplate != null) {
ResultItem footer = CreateResultItem(-1, ResultItemType.Footer, false, null);
items.Add(footer);
}}
It adds the ResultItem control to an internal ArrayList collection This is a publicly able collection that is exposed via a top-level Items property on Result, as shown in the following code Notice that we didn’t add the ResultItem controls to the Controls collection of Result in CreateBlankControlHierarchy This is handled by CreateResultItem, along with other things such as data binding and raising item-related events
reach-private Collection<ResultItem> items = new Collection<ResultItem>();
public Collection<ResultItem> Items{
get { return items ; }
}The items collection takes advantage of the List generic type removing the need to create
a custom strongly typed collection
The DataBind Scenario
The next mode of creating child controls inside the Result control class focuses on what happens when the Search control executes a search, sets the DataSource property of the Result control, and invokes its DataBind method, as shown in the following code The first task accomplished is clearing out child controls that might have been put into the collection manually and any
Trang 21information that might have been persisted to ViewState We also set the all-important
searchConducted Boolean value to true, so the control knows it is not in a blank control situation
public override void DataBind()
true to indicate to CreateControlHierarchy that we are in a data binding scenario We examine
the details of CreateControlHierarchy later in this chapter after we have covered our third mode,
which deals with a rendered Result control rehydrating at the beginning of postback
The Postback Scenario
The Result control’s CreateChildControls method, shown in the following snippet, is called
when a server control needs to build its control structure This could happen as part of the
blank control-building scenario or as part of postback from a client round-trip
override protected void CreateChildControls()
data bound, we do not need to create the control hierarchy We also check to see whether there
is content in the ViewState variable ResultItemCount If this is present, the page is coming back
via postback, and we can call CreateControlHierarchy to have it repopulate the control structure
based on ResultItemControl and have child controls retrieve their former values from ViewState If
the ViewState ResultItemCount variable is not present, we are in a blank control scenario, and
we let the code we have in RenderContents handle the blank mode situation
Creating a Control Hierarchy for Data Binding or Postback
Most of the heavy lifting to build the composite structure of the Result control occurs in the
following CreateControlHierarchy for the data-binding and postback scenarios This code is
typical of your run-of-the-mill data-bound control:
Trang 22private void CreateControlHierarchy(bool dataBind){
Controls.Clear();
SearchResponse result = null;
// Result items items = new Collection<ResultItem>();
int count = 0;
if (dataBind == true) {
if (DataSource == null) return;
if (temp != null) count = (int)temp;
}
if (HeaderTemplate != null) {
ResultItem headerItem = CreateResultItem(-1,ResultItemType.Header, false, null);
items.Add(headerItem);
} ResultItem statusItem = CreateResultItem(-1, ResultItemType.Status,dataBind, result);
items.Add(statusItem);
// loop through and create ResultItem controls for each of the // result elements from the Live Search web service result ResultItemType itemType = ResultItemType.Item;
for (int i = 0; i < count; i++)
Trang 23ResultItem item = CreateResultItem(i,
itemType, dataBind, searchResultItem);
// display pager if allowed by user and if results
// are greater than a page in length
if (DisplayPager == true && TotalResultsCount > PageSize)
property of itself DataSource is strongly typed in the implementation of Result to prevent
someone from accidentally assigning a DataSet or other type of collection to it
Trang 24/// <summary>
/// Data source which takes a SearchResponse to build display
/// </summary>
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), DefaultValue(null),
Bindable(true), Browsable(false)]
public LiveSearchService.SearchResponse DataSource{
get { return dataSource;
} set { dataSource = value;
}}The CreateControlHierarchy code, when data binding, pulls key parameters from the data source like the number of results, the offset, and the source result set The count variable is set
to the size of the Results array returned to ensure accurate looping in the template creation process If we are not in a data-binding scenario, yet we are creating the control hierarchy, we read ResultItemCount from the ViewState collection to set the count variable Having a count is all we need, because we go through a loop that creates the correct number of ResultItem controls for each of the search results, and the controls then are able to pull their previous information from ViewState
When the code loops through the result set items, it creates the required template for each item and data binds by calling the CreateResultItem method As the result items are processed, the HeaderTemplate, FooterTemplate, and SeparatorTemplates templates are checked for null values, whereas the StatusTemplate and ResultItemTemplate templates are not The reason for this difference is that ResultControl has two prewired template classes as default templates for the StatusTemplate and ItemTemplate if they are not specified by the user You can see this by examining the CreateResultItem method, which is responsible for creating the ResultItem control instances that house the final template content
Once we have looped through each ResultElement of the search query data set, we turn to creating the paging structure Here we call a different method to create the ResultItem instance that houses the paging structure by calling the CreatePagerItem method We also set up the ViewState to remember the count of elements added so we can rehydrate them from ViewState during postback
Creating ResultItem Controls
CreateControlHierarchy offloads most of the work to the CreateResultItem method, as shown
in the following code The CreateResultItem method is the true workhorse of the Result class
It creates the major structures, adds them to the Controls collection, and manages events and data binding
Trang 25private ResultItem CreateResultItem(int index, ResultItemType itemType,
bool dataBind, object dataItem)
// if no AlternatingItemTemplate, switch to Item type
// and pick up ItemTemplate
Trang 26case ResultItemType.Separator : selectedTemplate = SeparatorTemplate;
break;
case ResultItemType.Footer : selectedTemplate = FooterTemplate;
if (selectedTemplate != null) {
selectedTemplate.InstantiateIn(item);
} OnItemCreated(new ResultItemEventArgs(item));
Controls.Add(item);
if (dataBind) {
item.DataBind();
OnItemDataBound(new ResultItemEventArgs(item));
} return item;
}The first task for CreateResultItem is to determine what type of ResultItem it is creating using a switch statement The end result of the process is grabbing the correct template from the Result control’s template properties and assigning it to the selectedTemplate method variable For the Status and Item types, it also handles the case of a blank template by instantiating the built-
in default ResultStatusTemplate and ResultItemTemplate classes If the AlternatingItemTemplate property is blank, the Item type ResultItemTemplate default template is used
After template selection, a brand-new ResultItem control is created and is passed its index
in the parent Result control’s Item collection, as well as its type and a potentially valid data source After the ResultItem is minted, it receives the template control content via the Instantiate method of the ITemplate interface
The final step is to fire the required events Once the control is created, we raise an ItemCreated event and add the control to the Controls collection The final step is to call DataBind on the ResultItem if we are in a data-binding scenario, which then raises an ItemDataBound event
Creating the Child Pager Control
The Pager control is added to the Controls collection of the parent Result control in CreatePagerResultItem This special-purpose creation method creates a new ResultItem control and adds a configured Pager control to it as follows:
Trang 27private ResultItem CreatePagerResultItem()
{
ResultItem item = new ResultItem(-1, ResultItemType.Pager, null);
Pager pager = new Pager();
Pager is configured based on information from the web service query and information that
is exposed by the parent Result control The PageSize property is the number of entries listed
per page that are returned from the Live Search search results PagerBarRange is the number of
pages to display in numeric form at the bottom of the page to go along with the Previous and
Next buttons, if applicable PagerLinkStyle is of type ResultPagerLinkStyle declared as follows It
determines whether text links are displayed or text with DHTML is displayed
public enum ResultPagerLinkStyle
the Pager’s page numbers by inheriting from LinkButton and performing the same steps used
with the HighlightedHyperLink to add DHTML functionality
Notice that we don’t have to explicitly pass LiveSearchService.SearchResponse to Pager
in the CreatePagerResultItem method It has code inside of it to deal with calculating and
displaying the correct page ranges based on the TotalResultsCount, PageNumber, and PageSize
property values
Managing Paging
The Pager control that is part of the child control structure of a paging Result control will raise
the correct command events when a page link is clicked to change the page of results displayed
The command event raised by the Pager control is intercepted by the parent Result control via
the use of the OnBubbleEvent method override:
Trang 28protected override bool OnBubbleEvent(object source, EventArgs args){
// Handle events raised by children by overriding OnBubbleEvent
// (main purpose is to detect paging events) bool handled = false;
CommandEventArgs cea = args as CommandEventArgs;
// handle Page event by extracting new start index // and calling HandleSearch method, which does the // work of rebinding this control to the results // from the web service
if (cea.CommandName == "Page") {
StartIndex = Convert.ToInt32(cea.CommandArgument);
HandleSearch();
} return handled;
}The OnBubbleEvent implementation in Result grabs the index of the new page to display with the Result control and then calls HandleSearch, which actually talks to Live Search HandleSearch is similar to the method of the same name in the Search control, except that it doesn’t have to look up a Result control; it simply sets the DataSource and calls DataBind on itself
Styling the Result Control
After all of the child controls are created, either by CreateBlankControlHierarchy or CreateControlHierarchy, the styles exposed by the Result control are applied This is handled
in the RenderContents override discussed earlier At the end of RenderContents, the code invokes PrepareControlHierarchy to make this happen It loops through all the ResultItem controls and applies the appropriate Style object if the style was set on the Result control, as shown here:
protected void PrepareControlHierarchy(){
// apply all the appropriate style attributes // to the items in the result output
foreach (ResultItem item in this.Items) {
if (item.ItemType == ResultItemType.Header) {
if (HeaderStyle != null) item.ApplyStyle(HeaderStyle);
} else if (item.ItemType == ResultItemType.Status) {
Trang 29Because we know there is only one instance of the Pager server control stored as a ResultItem,
we apply PagerStyle directly to this instance, as shown in the preceding code Listing 12-6
shows the full source for the Result control
Listing 12-6 The Result.cs Class File
Trang 30using ControlsBook2Lib.CH12.LiveSearchControls.Design;
using LiveSearchService;
namespace ControlsBook2Lib.CH12.LiveSearchControls{
/// Render pager DHTML for page link buttons in pager
/// Not implemented but a place holder for extension /// </summary>
TextWithDHTML }
#if LICENSED RsaLicenseData(
"55489e7a-bff5-4b3c-8f21-c43fad861dfa",
"<RSAKeyValue><Modulus>mWpgckAepJAp4aU0AvEcGg3TdO+0VXws9LjiSCLpy7aQKD5V7uj49Exh1RtcB6TcuXxm0R6dw75VmKwyoGbvYT6btOIwQgqbLhci5LjWmWUPEdBRiYsOLD0h2POXs9xTvp4IDTKXYoP8GPDRKzklJuuxCbbUcooESQoYHp9ppbE=</Modulus><Exponent>AQAB
</Exponent></RSAKeyValue>"
), LicenseProvider(typeof(RsaLicenseProvider)),
#endif DefaultEvent("LiveSearchSearched") ]
public class Result : CompositeControl {
// constants private const int defaultPageSize = 10;
private const int defaultPagerBarRange = 4;
Trang 31private const int defaultPageNumber = 1;
// style property fields
private Style headerStyle;
private Style statusStyle;
private Style itemStyle;
private Style alternatingItemStyle;
private Style separatorStyle;
private Style pagerStyle;
private Style footerStyle;
private ResultPagerLinkStyle pagerLinkStyle =
ResultPagerLinkStyle.TextWithDHTML;
// Template property fields
private ITemplate headerTemplate;
private ITemplate statusTemplate;
private ITemplate itemTemplate;
private ITemplate alternatingItemTemplate;
private ITemplate separatorTemplate;
private ITemplate footerTemplate;
private bool searchConducted;
private SearchResponse dataSource;
private Collection<ResultItem> items = new Collection<ResultItem>();
Trang 32get { return HtmlTextWriterTag.Div;
} } #region Dispose pattern
#if LICENSED private bool _disposed;
/// You must override Dispose for controls derived from the License clsas /// </summary>
protected virtual void Dispose(bool disposing) {
if (!_disposed) {
if (disposing) {
//Dispose of additional unmanaged resources here
if (license != null) license.Dispose();
base.Dispose();
} license = null;
_disposed = true;
} }
#endif #endregion #region Search properties /// <summary>
/// Number of search results returned with query and displayed on page /// </summary>
[Description(
"Number of search results returned with query and displayed on page."), Category("Search"), DefaultValue(defaultPageSize)]
Trang 33virtual public int PageSize
Trang 34if (count == null) return 0;
else return (int)count;
} } /// <summary>
/// Search query string
if (query == null) return string.Empty;
else return (string)query;
} set { ViewState["Query"] = value;
} } #endregion #region Appearance properties /// <summary>
/// Display paging links at bottom of search results
if (pager == null) return true;
else return (bool)pager;
}
Trang 36#region Miscellaneous properties /// <summary>
/// Data source which takes a SearchResponse to build display
/// </summary>
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), DefaultValue(null),
Bindable(true), Browsable(false)]
public LiveSearchService.SearchResponse DataSource {
get { return dataSource;
} set { dataSource = value;
} } /// <summary>
/// Collection of child ResultItem controls /// </summary>
[Browsable(false)]
public Collection<ResultItem> Items {
get { return items;
} } #endregion #region Style properties /// <summary>
/// The style to be applied to header template
/// </summary>
[Category("Style"), Description("The style to be applied to header template."),DesignerSerializationVisibility(DesignerSerializationVisibility.Content), NotifyParentProperty(true),
PersistenceMode(PersistenceMode.InnerProperty), ]
public virtual Style HeaderStyle {
Trang 38public virtual Style ItemStyle {
get {
if (itemStyle == null) {
itemStyle = new Style();
if (IsTrackingViewState) ((IStateManager)itemStyle).TrackViewState();
} return itemStyle;
} } /// <summary>
/// The style to be applied to alternate item template
/// </summary>
[Category("Style"),Description("The style to be applied to alternate item template."),DesignerSerializationVisibility(DesignerSerializationVisibility.Content),NotifyParentProperty(true),
PersistenceMode(PersistenceMode.InnerProperty),]
public virtual Style AlternatingItemStyle {
get {
if (alternatingItemStyle == null) {
alternatingItemStyle = new Style();
if (IsTrackingViewState) ((IStateManager)alternatingItemStyle).TrackViewState();
} return alternatingItemStyle;
} } /// <summary>
/// The style to be applied to the separator template /// </summary>
[Category("Style"), Description("The style to be applied to the separator template."),DesignerSerializationVisibility(DesignerSerializationVisibility.Content), NotifyParentProperty(true),
PersistenceMode(PersistenceMode.InnerProperty), ]