Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 77 trang
THÔNG TIN TÀI LIỆU
Thông tin cơ bản
Định dạng
Số trang
77
Dung lượng
1,92 MB
Nội dung
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 - http://www.simpopdf.com 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 - http://www.simpopdf.com 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 - http://www.simpopdf.com 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 - http://www.simpopdf.com 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 - http://www.simpopdf.com 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(" "); 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 - http://www.simpopdf.com 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 - http://www.simpopdf.com 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 = "http://www.live.com"; Cameron_865-2C12.fm Page 598 Friday, February 22, 2008 1:05 PM Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com CHAPTER 12 ■ BUILDING A COMPLEX CONTROL 599 private const string LiveSearchWebSearchUrl = "http://search.live.com/results.aspx"; private const string LiveSearch25PtLogoImageUrl = "http://go.microsoft.com/fwlink/?LinkId=89151"; private const string LiveSearchLogoImageUrl = "http://go.microsoft.com/fwlink/?LinkId=89151"; 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 - http://www.simpopdf.com 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 - http://www.simpopdf.com [...]... ■ BUI LDI NG A C OM PLE X C O NT ROL protected 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; // // // // if { handle Page event by extracting new start index and calling HandleSearch method, which does the work... 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... rm.GetString("Search.searchButton.Text"); searchButton.Click += new EventHandler(SearchButtonClick); this .Controls. Add(searchButton); br = new LiteralControl(""); this .Controls. Add(br); } /// /// Overridden to ensure Controls collection is created before external access /// public override ControlCollection Controls { get { EnsureChildControls(); return base .Controls; } } } } Now that we have covered the... rebinding this control to the results from the web service (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... System.ComponentModel; System.Web.UI; System.Web.UI.WebControls; 6 19 Simpo PDF MergePage 620 SplitFebruary 22, 2008 1:05 PM Cameron_865-2C12.fm and Friday, Unregistered Version - http://www.simpopdf.com 620 CH APT ER 12 ■ BUI LDI NG A C OM PLE X C O NT ROL using ControlsBook2Lib.CH12.LiveSearchControls.Design; using LiveSearchService; namespace ControlsBook2Lib.CH12.LiveSearchControls { /// /// Determines... ToolboxData(" "), Designer(typeof(ResultDesigner)), #if LICENSED RsaLicenseData( "55489e7a-bff5-4b3c-8f21-c43fad861dfa", "mWpgckAepJAp4aU0AvEcGg3TdO+0VXws9LjiSCLpy7aQKD5V7uj 49Exh1RtcB6TcuXxm0R6dw75VmKwyoGbvYT6btOIwQgqbLhci5LjWmWUPEdBRiYsOLD0h2POX s9xTvp4IDTKXYoP8GPDRKzklJuuxCbbUcooESQoYHp9ppbE=AQAB " ), LicenseProvider(typeof(RsaLicenseProvider)),... 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... selectedTemplate method variable For the Status and Item types, it also handles the case of a blank template by instantiating the builtin 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... handled on this // postback searchHandled = true; } public event EventHandler LiveSearchSearched /// /// Protected method for invoking LiveSearchSearched event /// from within Result control /// /// Event arguments including search results protected virtual void OnLiveSearchSearched(LiveSearchSearchedEventArgs e) { EventHandler... This is a publicly reachable 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 items = new . 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() { . 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. 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