Tải bản đầy đủ (.pdf) (77 trang)

Pro Server Controls and AJAX Components phần 9 docx

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (1.92 MB, 77 trang )

CHAPTER 12 ■ BUILDING A COMPLEX CONTROL
591
Wrapping 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 configuration section we created earlier in this chapter.
Listing 12-3. The SearchUtility.cs Class File
using System;
using System.Configuration;
using System.ServiceModel;
using System.Threading;
using System.Web;
using LiveSearchService;
namespace ControlsBook2Lib.CH12.LiveSearchControls
{
/// <summary>
/// Utility class for abstracting Live Search web service proxy work
/// </summary>
public sealed class SearchUtility
{
private const string ConfigSectionName = "controlsBook2Lib/liveSearchControls";
/// <summary>
/// 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
LiveSearchConfigSectionHandler config =
(LiveSearchConfigSectionHandler)ConfigurationManager.GetSection(
ConfigSectionName);
if (config != null)
{
LiveSearchLicenseKey = config.License.LiveSearchLicenseKey;
LiveSearchWebServiceUrl = config.Url.LiveSearchWebServiceUrl;
}
Cameron_865-2C12.fm Page 591 Friday, February 22, 2008 1:05 PM
Simpo PDF Merge and Split Unregistered Version -
592
CHAPTER 12
■ BUILDING A COMPLEX CONTROL
// 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.
Cameron_865-2C12.fm Page 592 Friday, February 22, 2008 1:05 PM
Simpo PDF Merge and Split Unregistered Version -
CHAPTER 12 ■ BUILDING A COMPLEX CONTROL
593
Designing 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.
Cameron_865-2C12.fm Page 593 Friday, February 22, 2008 1:05 PM
Simpo PDF Merge and Split Unregistered Version -
594
CHAPTER 12
■ BUILDING A COMPLEX CONTROL
Figure 12-5. The architecture of the Live Search controls
Figure 12-6. Controls in action on an initial search
Search
Result
Search
1. Query params
2. Return NaoqhpEpai
Live Search
3. Locate Result control and data bind NaoqhpEpai
4. Display templates
5. Display data for paging
6. Display pages as command hyperlinks
1 23
Pager

Result
Cameron_865-2C12.fm Page 594 Friday, February 22, 2008 1:05 PM
Simpo PDF Merge and Split Unregistered Version -
CHAPTER 12 ■ BUILDING A COMPLEX CONTROL
595
Interacting 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
Search
2. Query params

3. Return NaoqhpEpai
Live Search
4. Data bind NaoqhpEpai
5. Display templates
1. Catch bubbled-up
command event
6. Pass data for paging
7. Display pages as
hyperlink commands
1 23
Pager
Result
Cameron_865-2C12.fm Page 595 Friday, February 22, 2008 1:05 PM
Simpo PDF Merge and Split Unregistered Version -
596
CHAPTER 12
■ BUILDING A COMPLEX CONTROL
control 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();
liveSearchLinkImage.ImageUrl = LiveSearchLogoImageUrl;
liveSearchLinkImage.NavigateUrl = LiveSearchWebPageUrl;
this.Controls.Add(liveSearchLinkImage);
LiteralControl br = new LiteralControl("<br>");
this.Controls.Add(br);
searchTextBox = new TextBox();

searchTextBox.Width = SearchTextBoxWidth;
//searchTextBox.TextChanged += new
// EventHandler(SearchTextBoxTextChanged);
this.Controls.Add(searchTextBox);
br = new LiteralControl("&nbsp;");
this.Controls.Add(br);
// search button Text is localized
ResourceManager rm = ResourceFactory.Manager;
searchButton = new Button();
searchButton.Text = rm.GetString("Search.searchButton.Text");
searchButton.Click += new EventHandler(SearchButtonClick);
this.Controls.Add(searchButton);
br = new LiteralControl("<br>");
this.Controls.Add(br);
}
Events are wired up in CreateChildControls as well. The Click event of searchButton
and the TextChanged event of searchTextBox are the events of interest. These are routed to the
SearchButtonClick and SearchTextBoxTextChanged private methods, respectively. All these
events handlers really accomplish is passing the search query text over to the HandleSearch
method, which does the majority of the work inside the Search control.
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:
Cameron_865-2C12.fm Page 596 Friday, February 22, 2008 1:05 PM
Simpo PDF Merge and Split Unregistered Version -
CHAPTER 12 ■ BUILDING A COMPLEX CONTROL
597
// 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);
}
In HandleSearch, there is code that looks at the RedirectToLiveSearch property to decide
whether to send the query back to the Live Search web site with Response.Redirect. The Query
property is put on the URL string using the q variable on the HTTP GET string to accomplish
this.
If we choose not to redirect the query to Live Search, we use the SearchUtility class to
receive a SearchResponse from the web service proxy code it wraps. The ResultControl property
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
Cameron_865-2C12.fm Page 597 Friday, February 22, 2008 1:05 PM
Simpo PDF Merge and Split Unregistered Version -
598
CHAPTER 12
■ BUILDING A COMPLEX CONTROL
event 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
using System;
using System.ComponentModel;

using System.Resources;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using LiveSearchService;
namespace ControlsBook2Lib.CH12.LiveSearchControls
{
/// <summary>
/// earch control displays input textbox and button to
///capture input and start search process.
/// </summary>
[ParseChildren(true),
ToolboxData("<{0}:Search runat=server></{0}:Search>"),
#if LICENSED
RsaLicenseData(
"55489e7a-bff5-4b3c-8f21-c43fad861dfa",
"<RSAKeyValue><Modulus>mWpgckAepJAp4aU0AvEcGg3TdO+0VXws9Lji
SCLpy7aQKD5V7uj49Exh1RtcB6TcuXxm0R6dw75VmKwyoGbvYT6btOIw
QgqbLhci5LjWmWUPEdBRiYsOLD0h2POXs9xTvp4IDTKXYoP8GPDRKz
klJuuxCbbUcooESQoYHp9ppbE=</Modulus><Exponent>AQAB</Exponent>
</RSAKeyValue>"
),
LicenseProvider(typeof(RsaLicenseProvider)),
#endif
DefaultEvent("LiveSearchSearched"),Designer(typeof(SearchDesigner))]
public class Search : CompositeControl
{
private const string LiveSearchWebPageUrl = "";
Cameron_865-2C12.fm Page 598 Friday, February 22, 2008 1:05 PM
Simpo PDF Merge and Split Unregistered Version -

CHAPTER 12 ■ BUILDING A COMPLEX CONTROL
599
private const string LiveSearchWebSearchUrl =
" /> private const string LiveSearch25PtLogoImageUrl =
" /> private const string LiveSearchLogoImageUrl =
" /> 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;
#if LICENSED
private License license;
#endif
/// <summary>
/// Default constructor for Search control
/// </summary>
public Search()
{
#if LICENSED
// initiate license validation
license =
LicenseManager.Validate(typeof(Search), this);
#endif
}
#if LICENSED
private bool _disposed;
/// <summary>
/// Override Dispose to clean up resources.

/// </summary>
public sealed override void Dispose()
{
//Dispose of any unmanaged resources
Dispose(true);
GC.SuppressFinalize(this);
}
Cameron_865-2C12.fm Page 599 Friday, February 22, 2008 1:05 PM
Simpo PDF Merge and Split Unregistered Version -
600
CHAPTER 12
■ BUILDING A COMPLEX CONTROL
/// <summary>
/// You must override Dispose for controls derived from the License class
/// </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
/// <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")]
Cameron_865-2C12.fm Page 600 Friday, February 22, 2008 1:05 PM
Simpo PDF Merge and Split Unregistered Version -
CHAPTER 12 ■ BUILDING A COMPLEX CONTROL
601

virtual public string Query
{
get
{
EnsureChildControls();
return searchTextBox.Text;
}
set
{
EnsureChildControls();
searchTextBox.Text = value;
}
}
/// <summary>
/// Redirect search query to Live Search site web pages.
/// </summary>
[DescriptionAttribute("Redirect search query to Live Search site web pages."),
CategoryAttribute("Search")]
virtual public bool RedirectToLiveSearch
{
get
{
object redirect = ViewState["RedirectToLiveSearch"];
if (redirect == null)
return DefaultRedirectToLiveSearchValue;
else
return (bool)redirect;
}
set
{

ViewState["RedirectToLiveSearch"] = value;
}
}
/// <summary>
/// Click event handler for search button
/// </summary>
/// <param name="s">Search button</param>
/// <param name="e">Event arguments</param>
protected void SearchButtonClick(object source, EventArgs e)
{
HandleSearch();
}
Cameron_865-2C12.fm Page 601 Friday, February 22, 2008 1:05 PM
Simpo PDF Merge and Split Unregistered Version -
602
CHAPTER 12
■ BUILDING A COMPLEX CONTROL
private 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();
sourceRequests[0].Count = resControl.PageSize;
//Specifies the number of results to return from offset
sourceRequests[0].Source = SourceType.Web;
//new search, always reset
sourceRequests[0].Offset = 0; //start index for returned results
sourceRequests[0].ResultFields = ResultFieldMask.All |
ResultFieldMask.DateTime;
// get search results from Live Search WCF service proxy
SearchResponse searchResponse =
SearchUtility.SearchLiveSearchService(
Query, sourceRequests);
// raise search results for any interested parties as well
OnLiveSearchSearched(new LiveSearchSearchedEventArgs(searchResponse));
Cameron_865-2C12.fm Page 602 Friday, February 22, 2008 1:05 PM
Simpo PDF Merge and Split Unregistered Version -
CHAPTER 12 ■ BUILDING A COMPLEX CONTROL

603
// databind search results with the Result control
// we are linked with
resControl.Query = Query;
resControl.PageNumber = 0;
resControl.DataSource = searchResponse;
resControl.DataBind();
}
// set bool that tells us the search has been handled on this
// postback
searchHandled = true;
}
public event EventHandler<LiveSearchSearchedEventArgs>
LiveSearchSearched
/// <summary>
/// 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)
{
EventHandler<LiveSearchSearchedEventArgs> evnt = LiveSearchSearched;
if (evnt != null)
evnt(this, e);
}
/// <summary>
/// Called by framework for composite controls to create control hierarchy
/// </summary>
protected override void CreateChildControls()
{

liveSearchLinkImage = new HyperLink();
liveSearchLinkImage.ImageUrl = LiveSearchLogoImageUrl;
liveSearchLinkImage.NavigateUrl = LiveSearchWebPageUrl;
this.Controls.Add(liveSearchLinkImage);
LiteralControl br = new LiteralControl("<br>");
this.Controls.Add(br);
searchTextBox = new TextBox();
searchTextBox.Width = SearchTextBoxWidth;
//searchTextBox.TextChanged += new
// EventHandler(SearchTextBoxTextChanged);
this.Controls.Add(searchTextBox);
Cameron_865-2C12.fm Page 603 Friday, February 22, 2008 1:05 PM
Simpo PDF Merge and Split Unregistered Version -
604
CHAPTER 12
■ BUILDING A COMPLEX CONTROL
br = new LiteralControl("&nbsp;");
this.Controls.Add(br);
// search button Text is localized
ResourceManager rm = ResourceFactory.Manager;
searchButton = new Button();
searchButton.Text = rm.GetString("Search.searchButton.Text");
searchButton.Click += new EventHandler(SearchButtonClick);
this.Controls.Add(searchButton);
br = new LiteralControl("<br>");
this.Controls.Add(br);
}
/// <summary>
/// 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
Cameron_865-2C12.fm Page 604 Friday, February 22, 2008 1:05 PM
Simpo PDF Merge and Split Unregistered Version -
CHAPTER 12 ■ BUILDING A COMPLEX CONTROL
605

stuff 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
Cameron_865-2C12.fm Page 605 Friday, February 22, 2008 1:05 PM
Simpo PDF Merge and Split Unregistered Version -
606
CHAPTER 12
■ BUILDING A COMPLEX CONTROL
data-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
{

/// <summary>
/// Enum which indicates what type of content/template/styles the
/// ResultItem control represents
/// </summary>
public enum ResultItemType
{
/// <summary>
/// Represents top of control output
/// </summary>
Header = 0,
/// <summary>
/// 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,

Cameron_865-2C12.fm Page 606 Friday, February 22, 2008 1:05 PM
Simpo PDF Merge and Split Unregistered Version -
CHAPTER 12 ■ BUILDING A COMPLEX CONTROL
607
/// <summary>
/// Represents bottom of control output below paging area
/// </summary>
Footer
}
/// <summary>
/// Primary child control of Result that contains all of the various templates
/// when instantiated.
/// </summary>
public class ResultItem : CompositeControl
{
private object dataItem;
private ResultItemType itemType;
private int itemIndex;
/// <summary>
/// Default constructor of ResultItem control
/// </summary>
/// <param name="index">
/// Index of control in collection of ResultItem controls under Result</param>
/// <param name="type">
/// Type of template the ResultItem control represents</param>
/// <param name="dataItem">Data from search query</param>
public ResultItem(int index, ResultItemType type, object dataItem)
{
this.itemType = type;
this.dataItem = dataItem;

this.itemIndex = index;
}
/// <summary>
/// Data from search query
/// </summary>
public object DataItem
{
get
{
return dataItem;
}
set
{
dataItem = value;
}
}
Cameron_865-2C12.fm Page 607 Friday, February 22, 2008 1:05 PM
Simpo PDF Merge and Split Unregistered Version -
608
CHAPTER 12
■ BUILDING A COMPLEX CONTROL
/// <summary>
/// Index of control in collection of ResultItem controls under Result
/// </summary>
public int ItemIndex
{
get
{
return itemIndex;
}

}
/// <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

{
Cameron_865-2C12.fm Page 608 Friday, February 22, 2008 1:05 PM
Simpo PDF Merge and Split Unregistered Version -
CHAPTER 12 ■ BUILDING A COMPLEX CONTROL
609
get
{
return item;
}
}
}
}
One thing to highlight regarding the Result control is how it binds different levels of data
from the LiveSearchResult data source to the ResultItem based on its associated control
template 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
if (!searchConducted)
{
CreateBlankControlHierarchy();
}
// prep all template styles
PrepareControlHierarchy();
// render all child controls
base.RenderContents(writer);
}
Cameron_865-2C12.fm Page 609 Friday, February 22, 2008 1:05 PM
Simpo PDF Merge and Split Unregistered Version -
610
CHAPTER 12
■ BUILDING A COMPLEX CONTROL
After 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 reach-
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.
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
Cameron_865-2C12.fm Page 610 Friday, February 22, 2008 1:05 PM
Simpo PDF Merge and Split Unregistered Version -
CHAPTER 12 ■ BUILDING A COMPLEX CONTROL
611
information 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()
{
base.OnDataBinding(System.EventArgs.Empty);
Controls.Clear();
ClearChildViewState();
TrackViewState();
searchConducted = true;
CreateControlHierarchy(true);
ChildControlsCreated = true;
}
CreateControlHierarchy is used by DataBind to load up the control content and execute
DataBind methods on each individual template. The Boolean value passed by DataBind is set to
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()
{

if (searchConducted == false &&
ViewState["ResultItemCount"] != null)
{
CreateControlHierarchy(false);
} }
The CreateChildControls method checks the searchConducted Boolean value to determine
whether it has been called as part of the data-binding scenario. If the page has been manually
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:
Cameron_865-2C12.fm Page 611 Friday, February 22, 2008 1:05 PM
Simpo PDF Merge and Split Unregistered Version -
612
CHAPTER 12
■ BUILDING A COMPLEX CONTROL
private 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;
result = DataSource;
// set ViewState values for read-only props
ViewState["TotalResultsCount"] =
result.Responses[0].Total;
ViewState["Offset"] = result.Responses[0].Offset;
ViewState["Source"] = result.Responses[0].Source;
count = result.Responses[0].Results.Length;
ViewState["ResultItemCount"] = count;
}
else
{
object temp = ViewState["ResultItemCount"];
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++)
Cameron_865-2C12.fm Page 612 Friday, February 22, 2008 1:05 PM
Simpo PDF Merge and Split Unregistered Version -
CHAPTER 12 ■ BUILDING A COMPLEX CONTROL
613
{
if (separatorTemplate != null)
{
ResultItem separator =
CreateResultItem(-1, ResultItemType.Separator, false, null);
items.Add(separator);
}
LiveSearchService.Result searchResultItem = null;
if (dataBind == true)
{
searchResultItem = result.Responses[0].Results[i];
}
ResultItem item = CreateResultItem(i,
itemType, dataBind, searchResultItem);
items.Add(item);
// swap between item and alternatingitem types
if (itemType == ResultItemType.Item)
itemType = ResultItemType.AlternatingItem;
else
itemType = ResultItemType.Item;
}
// display pager if allowed by user and if results
// are greater than a page in length
if (DisplayPager == true && TotalResultsCount > PageSize)
{

ResultItem pager = CreatePagerResultItem();
items.Add(pager);
}
if (FooterTemplate != null)
{
ResultItem footer = CreateResultItem(-1, ResultItemType.Footer,
false, null);
items.Add(footer);
}
}
If we are in data-binding mode based on the passed-in Boolean parameter, the Result
control examines the LiveSearchService.SearchResponse instance linked to the DataSource
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.
Cameron_865-2C12.fm Page 613 Friday, February 22, 2008 1:05 PM
Simpo PDF Merge and Split Unregistered Version -
614
CHAPTER 12
■ BUILDING A COMPLEX CONTROL
/// <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.
Cameron_865-2C12.fm Page 614 Friday, February 22, 2008 1:05 PM
Simpo PDF Merge and Split Unregistered Version -
CHAPTER 12 ■ BUILDING A COMPLEX CONTROL
615
private ResultItem CreateResultItem(int index, ResultItemType itemType,
bool dataBind, object dataItem)
{
ITemplate selectedTemplate;
switch (itemType)
{
case ResultItemType.Header :
selectedTemplate = HeaderTemplate;
break;
case ResultItemType.Status :
if (StatusTemplate == null)
{
// if no StatusTemplate, pick up the default
// template ResultStatusTemplate
selectedTemplate = new ResultStatusTemplate();
}
else
selectedTemplate = StatusTemplate;
break;
case ResultItemType.Item :
if (ItemTemplate == null)
{
// if no ItemTemplate, pick up the default

// template ResultItemTemplate
selectedTemplate = new ResultItemTemplate();
}
else
selectedTemplate = ItemTemplate;
break;
case ResultItemType.AlternatingItem :
selectedTemplate = AlternatingItemTemplate;
if (selectedTemplate == null)
{
// if no AlternatingItemTemplate, switch to Item type
// and pick up ItemTemplate
itemType = ResultItemType.Item;
selectedTemplate = ItemTemplate;
if (selectedTemplate == null)
{
// if that doesn't work, pick up the default
// template ResultItemTemplate
selectedTemplate = new ResultItemTemplate();
}
}
break;
Cameron_865-2C12.fm Page 615 Friday, February 22, 2008 1:05 PM
Simpo PDF Merge and Split Unregistered Version -

×