XML Web Service Based Server Controls The StockGrid Example


XML Web Service “Based Server Controls ” The StockGrid Example

The most common way to use Web services is to reference them and invoke their methods directly. Building dynamic Web pages based on information retrieved from a Web service is also very useful. In these scenarios, you can provide an easier interface to a Web service by developing an associated server control. The server control can encapsulate the tasks of referencing and invoking a Web service by using a built-in client proxy class, and it can hide those details from the page developer. In addition, a control can implement smart logic to decide when to invoke the Web service and when to instead use cached results from a previous call without rendering outdated information. This greatly improves the performance of the Web application by reducing the time it spends making Web requests , especially when using cached results is appropriate. A server control makes it easy to reuse the smart logic across pages and applications.

The sample page shown in Listing 18-5 contains a user interface (UI) based on the stock data retrieved at a regular interval from StockWebService . Figure 18-3 shows the StockGrid control in customize mode, and Figure 18-4 shows the control in normal mode.

Listing 18-5 StockGridTest.aspx is used to demonstrate the StockGrid control.
 <%@ Page language="c#" %> <%@ Register TagPrefix="mspsc" Assembly="MSPress.ServerComponents"     Namespace="MSPress.ServerComponents" %> <html>   <head>     <title>StockGrid Sample</title>     <link href="http://Default.css" type="text/css" rel="stylesheet" />   </head>   <body>     <form id="ImageLabelTest" method="post" runat="server">       <p>         <mspsc:StockGrid id="StockGrid2" runat="server" Width="210px"             Font-Size="8pt" Font-Names="Verdana" GridLines="Horizontal"             BorderStyle="None" StockWebServiceUrl=               "http://localhost/BookWeb/Chapter18/StockWebService.asmx">           <AlternateStockRowStyle BackColor="Silver"/>           <StockHeaderStyle Font-Bold="True" ForeColor="White"               BackColor="Navy"/>           <StockRowStyle BackColor="Gainsboro"/>         </mspsc:StockGrid>         <hr>         <asp:LinkButton id="Button1" runat="server" Text="Refresh"/>      </p>     </form>   </body> </html> 
Figure 18-3. The StockGrid control in customize mode, allowing the user to select a sequence of stock symbols

graphics/f18hn03.jpg

Figure 18-4. The StockGrid control in normal mode, showing information about the selected stock symbols

graphics/f18hn04.jpg

Implementing the Server Control

The StockGrid server control renders HTML based on the data retrieved by calling the GetCurrentStockData method of StockWebService . In particular, this example demonstrates how a server control can be associated with a Web service and how a control can intelligently determine when to access the Web service. The control's implementation is based on several concepts that we first introduced in Part III of this book, "Server Controls ”Nuts and Bolts."

Listing 18-6 lists the code of the server control.

Listing 18-6 StockGrid.cs contains the implementation of the StockGrid control.
 using System; using System.Collections; using System.ComponentModel; using System.ComponentModel.Design; using System.Drawing; using System.Web.UI; using System.Web.UI.WebControls; using MSPress.ServerComponents.Proxy; namespace MSPress.ServerComponents {     [     DefaultProperty("StockWebServiceUrl"),     Designer(typeof(MSPress.ServerComponents.Design.StockGridDesigner),         typeof(IDesigner))     ] 
 public class StockGrid : WebControl, INamingContainer {         private DataGrid _dataGrid;         private Panel _customizePanel;         private TextBox _symbolsTextBox;         private StockDataSnapShot _stockData;         private bool _updateData;         [         Category("Style"),         Description("Style applied to alternate rows "),         DesignerSerializationVisibility(DesignerSerializationVisibility.Content),         NotifyParentProperty(true),         PersistenceMode(PersistenceMode.InnerProperty)         ]         public TableItemStyle AlternateStockRowStyle {             get {                 EnsureChildControls();                 return _dataGrid.AlternatingItemStyle;             }         }      [         Category("Appearance"),         DefaultValue(-1),         Description("Padding inside cells in the stock table")         ]         public virtual int CellPadding {             get {                 if (ControlStyleCreated == false) {                     return -1;                 }                 return ((TableStyle)ControlStyle).CellPadding;             }             set {                 ((TableStyle)ControlStyle).CellPadding = value;             }         }  [         Category("Appearance"),         DefaultValue(0),         Description("Spacing between cells in the stock table")         ]         public virtual int CellSpacing {             get {                 if (ControlStyleCreated == false) {                     return 0;                 }                 return ((TableStyle)ControlStyle).CellSpacing;             }             set {                 ((TableStyle)ControlStyle).CellSpacing = value;             }         }         public override ControlCollection Controls {             get {                 EnsureChildControls();                 return base.Controls;             }         }  [         Browsable(false),         DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)         ]         public virtual bool CustomizeMode {             get {                 object b = ViewState["CustomizeMode"];                 return (b == null) ? true : (bool)b;             }            set {                 ViewState["CustomizeMode"] = value;             }         }         [         Category("Appearance"),         DefaultValue(GridLines.Both),         Description("The style of the grid inside the stock table")         ]         public virtual GridLines GridLines {             get {                 if (ControlStyleCreated == false) {                     return GridLines.Both;                 }                 return ((TableStyle)ControlStyle).GridLines;             }             set {                 ((TableStyle)ControlStyle).GridLines = value;             }         }         private DateTime LastUpdateTime {             get {                 object d = ViewState["LastUpdateTime"];                 return (d == null) ? DateTime.Now : (DateTime)d;             }             set {                 ViewState["LastUpdateTime"] = value;             }         }         [         Category("Style"),         Description("Style applied to the stock table footer"),         DesignerSerializationVisibility(DesignerSerializationVisibility.Content), NotifyParentProperty(true),         PersistenceMode(PersistenceMode.InnerProperty)         ]         public TableItemStyle StockFooterStyle {             get {                 EnsureChildControls();                 return _dataGrid.FooterStyle;             }         }          [         Category("Style"),         Description("Style applied to the stock table header"),         DesignerSerializationVisibility(DesignerSerializationVisibility.Content),         NotifyParentProperty(true),         PersistenceMode(PersistenceMode.InnerProperty)         ]         public TableItemStyle StockHeaderStyle {             get {                 EnsureChildControls();                 return _dataGrid.HeaderStyle;             }         }         [         Category("Style"),         Description("Style applied to rows containing stock information"),         DesignerSerializationVisibility(DesignerSerializationVisibility.Content),         NotifyParentProperty(true),         PersistenceMode(PersistenceMode.InnerProperty)         ]         public TableItemStyle StockRowStyle {             get {                 EnsureChildControls();                 return _dataGrid.ItemStyle;             }         }         [         Bindable(true),         Category("Default"),         DefaultValue(""),         Description("The URL of the Stock Web service to access.")         ] public virtual string StockWebServiceUrl {             get {                 string s = (string)ViewState["StockWebServiceUrl"];                 return (s == null) ? String.Empty : s;             }            set {                 if (StockWebServiceUrl.Equals(value) == false) {                     ViewState["StockWebServiceUrl"] = value;                     _updateData = true;                 }             }         }         [         Browsable(false),         DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)         ]         public virtual string Symbols {             get {                 string s = (string)ViewState["Symbols"];                 return (s == null) ? String.Empty : s;             }             set {                 if (Symbols.Equals(value) == false) {                     ViewState["Symbols"] = value;                     _updateData = true;                 }             }         }         [         Category("Behavior"),         DefaultValue(120000),         Description("The interval (in milliseconds) at which the " +             "StockWebService is accessed")         ]         public virtual int UpdateInterval {             get {                 object i = ViewState["UpdateInterval"];                 return (i == null) ? 120000 : (int)i;             }             set {                 if (value < 0) {                     throw new ArgumentOutOfRangeException("value");                 } ViewState["UpdateInterval"] = value;             }         }         protected override void CreateChildControls() {             Controls.Clear();             LiteralControl customizeText =                 new LiteralControl("<p>Enter the stock symbols you " +                     "wish to track, separated by ';':</p>");             LiteralControl separatorText =                  new LiteralControl("&nbsp;");             LinkButton okButton = new LinkButton();             okButton.Text = "Update";             okButton.Click += new EventHandler(this.OKButton_Click);             BoundColumn symbolColumn = new BoundColumn();             symbolColumn.HeaderText = "Symbol";             symbolColumn.DataField = "Symbol";             BoundColumn valueColumn = new BoundColumn();             valueColumn.HeaderText = "Value";             valueColumn.DataField = "Value";             valueColumn.DataFormatString = "{0:0.000}";             BoundColumn changeColumn = new BoundColumn();             changeColumn.HeaderText = "Change";             changeColumn.DataField = "Change";             changeColumn.DataFormatString = "{0:0.000}";             _symbolsTextBox = new TextBox();             _symbolsTextBox.Width = Unit.Percentage(70.0);             _customizePanel = new Panel();             _customizePanel.EnableViewState = false;             _customizePanel.Controls.Add(customizeText);             _customizePanel.Controls.Add(_symbolsTextBox);             _customizePanel.Controls.Add(separatorText);             _customizePanel.Controls.Add(okButton);             _dataGrid = new DataGrid();             _dataGrid.ShowFooter = true;             _dataGrid.AutoGenerateColumns = false;             _dataGrid.Columns.Add(symbolColumn);             _dataGrid.Columns.Add(valueColumn);             _dataGrid.Columns.Add(changeColumn);             _dataGrid.ItemCreated += new                  DataGridItemEventHandler(this.DataGrid_ItemCreated); _dataGrid.ItemDataBound += new                  DataGridItemEventHandler(this.DataGrid_ItemDataBound);             Controls.Add(_customizePanel);             Controls.Add(_dataGrid);         }         protected override Style CreateControlStyle() {             TableStyle controlStyle = new TableStyle(ViewState);             controlStyle.GridLines = GridLines.Both;             controlStyle.CellSpacing = 0;             return controlStyle;         }         private void CustomizeButton_Click(object sender,              EventArgs e) {             CustomizeMode = true;         }         private void DataGrid_ItemCreated(object sender,             DataGridItemEventArgs e) {             if (e.Item.ItemType == ListItemType.Footer) {                 e.Item.Cells.RemoveAt(2);                 e.Item.Cells.RemoveAt(1);                 TableCell cell = e.Item.Cells[0];                 cell.ColumnSpan = 3;                 Label updateLabel = new Label();                 LinkButton customizeButton = new LinkButton();                 customizeButton.Text = "Customize";                 customizeButton.Click +=                     new EventHandler(this.CustomizeButton_Click);                 cell.Controls.Add(updateLabel);                 cell.Controls.Add(new LiteralControl("<br>"));                 cell.Controls.Add(customizeButton);             }         }         private void DataGrid_ItemDataBound(object sender,             DataGridItemEventArgs e) { if ((e.Item.ItemType == ListItemType.Item)                  (e.Item.ItemType == ListItemType.AlternatingItem)) {                 double change =                      (double)DataBinder.Eval(e.Item.DataItem, "Change");                 if (change != 0.0) {                     TableCell changeCell = e.Item.Cells[2];                     if (change > 0.0) {                         changeCell.ForeColor = Color.Green;                     }                     else {                         changeCell.ForeColor = Color.Red;                     }                 }             }             else if (e.Item.ItemType == ListItemType.Footer) {                 Label updateLabel = (Label)e.Item.Cells[0].Controls[0];                 DateTime dt;                 if (_stockData != null) {                     dt = _stockData.SnapShotTime;                 }                 else {                     dt = DateTime.Now;                 }                 updateLabel.Text =                       "Last updated on " + String.Format("{0:T}", dt);             }         }         private void OKButton_Click(object sender, EventArgs e) {             Symbols = _symbolsTextBox.Text.Trim();             CustomizeMode = false;             _updateData = true;         }         protected override void OnPreRender(EventArgs e) {             base.OnPreRender(e);             if (StockWebServiceUrl.Length == 0) {                 return;             }             bool isCustomizeMode =                  CustomizeMode  (Symbols.Length == 0); if (isCustomizeMode) {                 return;             }             if (ViewState["LastUpdateTime"] == null) {                 // This is the first time around.                 _updateData = true;             }             else {                 // If it's been longer than the selected update                  // interval since the last update, it's time to                  // update now.                 DateTime lastUpdateTime = LastUpdateTime;                 DateTime now = DateTime.Now;                 TimeSpan timeSpan =                     new TimeSpan(now.Ticks - lastUpdateTime.Ticks);                 if (timeSpan.Milliseconds > UpdateInterval) {                     _updateData = true;                 }             }             if (_updateData) {                 StockWebService service = new StockWebService();                 // Use the user-selected URL, not the one embedded in                  // the proxy.                 service.Url = StockWebServiceUrl;                 try {                     _stockData = service.GetCurrentStockData(Symbols);                     // Keep track of when the control was last updated.                     LastUpdateTime = DateTime.Now;                     // Create a data source that can be used for data                      // binding. In particular, create BindableStockData                      // objects, which have properties for each field                      // in a StockData object returned by the Web                      // service.                     ArrayList array = new ArrayList();                     for (int i = 0; i < _stockData.Stocks.Length;                          i++) {                         array.Add(new BindableStockData(_stockData.Stocks[i]));                   } // Bind the DataGrid with the stock data.                     _dataGrid.DataSource = array;                     _dataGrid.DataBind();                 }                 catch {                 }             }         }          protected override void Render(HtmlTextWriter writer) {             if (StockWebServiceUrl.Length == 0) {                 return;             }             WebControl visibleControl;             if (ControlStyleCreated == false) {                 // Force creation of ControlStyle because its default                  // state has nondefault (or nonempty) values.                 Style dummyStyle = ControlStyle;             }             string symbols = Symbols;             bool isCustomizeMode =                  CustomizeMode  (symbols.Length == 0);             if (isCustomizeMode) {                 visibleControl = _customizePanel;                 _customizePanel.Visible = true;                 _dataGrid.Visible = false;                 _symbolsTextBox.Text = symbols;             }             else {                 visibleControl = _dataGrid;                 _customizePanel.Visible = false;                 _dataGrid.Visible = true;             }             visibleControl.ApplyStyle(ControlStyle);             visibleControl.CopyBaseAttributes(this);             RenderContents(writer);         }         private sealed class BindableStockData {             private StockData _stock;             public BindableStockData(StockData stock) {                 _stock = stock;             }             public double Change {                 get { return _stock.Change; }             }             public string Symbol {                 get { return _stock.Symbol; }             }             public double Value {                 get { return _stock.Value; }             }         }     } } 

The StockGrid control is a composite control that implements the composite control pattern described in Chapter 12, "Composite Controls." This includes implementing the INamingContainer interface, overriding the Controls property to ensure that its contained child controls have been created, and overriding CreateChildControls to implement the logic needed to create its child controls on demand. The StockGrid control reuses the functionality of the DataGrid control to provide style properties such as StockRowStyle , GridLines , and so on. StockGrid also implements event handlers to handle events being raised by the controls it contains, just as a page developer would if these controls were used directly on a page.

The StockGrid control implementation illustrates the following concepts:

  • Provides two views ”the customize view (shown in Figure 18-3), which allows the user to select the stock symbols to track, and the normal view (shown in Figure 18-4), which lists the selected stocks and their values. The control automatically renders its customize view when it does not have any stock symbols to track. In addition, the control allows the page developer to explicitly activate this view via the CustomizeMode property. StockGrid contains a Panel for its customize view and a DataGrid for its normal view. The control toggles the visibility of the Panel and DataGrid controls by overriding the Render method.

  • Fills its DataGrid with stock data retrieved from a StockWebService . The control offers the StockWebServiceUrl property, which the page developer must set to select the appropriate URL of the specific Web service implementation to use. This approach allows the control to be much more flexible than the alternative ”having built-in knowledge about the location of one specific Web service. Any Web service can be chosen , as long as it implements the expected StockWeb ­Service contract.

  • Allows the page developer to programmatically specify the parameter data sent to the Web service's method. In this example, the control offers the Symbols property, which allows the page developer to programmatically specify the stocks to track instead of obliging the user to select them through the control's customize view. This also gives the page developer greater flexibility and control over Web service access.

  • The UpdateInterval property also adds to the flexibility of the StockGrid control. A StockWebService updates its data at regular intervals. Therefore, it is unnecessary to access the Web service on every request made to the page. Instead, the StockGrid control is smart about caching the data. The control accesses the Web service only if a sufficient interval has passed to warrant new stock data. This reduces network traffic, thereby improving the request time for the page. The UpdateInterval property allows the page developer to override the control's default settings for the Web service access behavior.

  • Uses a DataGrid control as part of its implementation so that it can take advantage of the grid's data-binding model. StockGrid calls the DataBind method of its DataGrid only when it accesses the Web service successfully. The DataGrid control is able to re-create itself from view state in the absence of a live data source. This allows StockGrid to generate its table of stocks even when it does not access the Web service, without implementing its own caching logic. The control performs the data binding to display updated values in its override of the OnPreRender method. This method follows normal user code that might change the properties of the StockGrid control (such as the Symbols property) earlier in the page life cycle but still enables the control to prepare the contained DataGrid for rendering.

StockGrid uses a Web service client proxy generated by using the wsdl.exe tool with the following command:

 wsdl/language:CS/namespace:MSPress.ServerComponents.Proxy /out:StockWebService.cs "http://localhost/BookWeb/Chapter18/StockWebService.asmx" 

The generated proxy is a class that contains the same GetCurrentStockData method that is exposed as a Web method by the Web service. It is included along with the other sample code for this chapter. In addition, it contains the StockDataSnapShot and StockData classes used as the return value of the method. The array of StockData objects inside the snapshot returned by the Web service provides the data needed to bind the contained DataGrid control. However, wsdl.exe generates public fields in these classes. Because the data-binding model in Web Forms works against properties only, StockGrid contains a BindableStockData class that is a thin wrapper over an actual StockData instance. The properties in the wrapper class map to the corresponding fields of the StockData instance that it wraps. An ArrayList of BindableStockData instances is then used as the data source for the DataGrid control.

Implementing the Control Designer

The StockGrid control is associated with its own control designer, StockGrid ­Designer . The designer uses dummy data to generate the design-time UI, rather than invoking the Web service at design time. This example also demonstrates how a designer associated with a control that offers multiple views can provide a mechanism to illustrate both views at design time. Figure 18-5 shows StockGrid in Visual Studio .NET.

Figure 18-5. The StockGrid control selected on the design surface in StockGridTest.aspx

graphics/f18hn05.jpg

The designer implementation shown in Listing 18-7 is based on the concepts introduced in Chapter 15, "Design-Time Functionality."

Listing 18-7 StockGridDesigner.cs shows the implementation of the StockGridDesigner .
 usingSystem; usingSystem.ComponentModel; usingSystem.ComponentModel.Design; usingSystem.Diagnostics; usingSystem.Text; usingSystem.Web.UI.Design; usingSystem.Web.UI; usingSystem.Web.UI.WebControls; usingMSPress.ServerComponents; namespaceMSPress.ServerComponents.Design{ publicclassStockGridDesigner:ControlDesigner{ 
 privateDesignerVerb_toggleVerb; privateDesignTimeStockObject[]_designTimeData; publicoverrideDesignerVerbCollectionVerbs{ get{ if(_toggleVerb==null){ //Addthecustomverbthefirsttimearound. _toggleVerb=newDesignerVerb("ToggleView", newEventHandler(this.OnToggleView)); DesignerVerbCollectionverbs=base.Verbs; verbs.Add(_toggleVerb); } returnbase.Verbs; } } publicoverridestringGetDesignTimeHtml(){ StockGridstockGrid=(StockGrid)Component; if(stockGrid.StockWebServiceUrl.Length==0){ returnCreatePlaceHolderDesignTimeHtml("YoumustsettheStockWebServiceUrlproperty."); } ControlCollectioncontrols=stockGrid.Controls; if(stockGrid.CustomizeMode==false){ if(_designTimeData==null){ _designTimeData= newDesignTimeStockObject[]{ newDesignTimeStockObject("X",3.09,-1.0), newDesignTimeStockObject("Y",10.10,0.0), newDesignTimeStockObject("Z",2.14,0.14) }; } DataGridcontainedGrid=(DataGrid)stockGrid.Controls[1]; try{ containedGrid.DataSource=_designTimeData; containedGrid.DataBind(); } catch{ } } returnbase.GetDesignTimeHtml(); } publicoverridevoidInitialize(IComponentcomponent){ if(!(componentisStockGrid)){ thrownewArgumentException("ComponentmustbeanStockGrid", "component"); } base.Initialize(component); } privatevoidOnToggleView(objectsender,EventArgse){ StockGridstockGrid=(StockGrid)Component; if(stockGrid.CustomizeMode){ stockGrid.CustomizeMode=false; stockGrid.Symbols="abc"; } else{ stockGrid.CustomizeMode=true; stockGrid.Symbols=String.Empty; } UpdateDesignTimeHtml(); } privateclassDesignTimeStockObject{ privatestring_symbol; privatedouble_value; privatedouble_change; publicDesignTimeStockObject(stringsymbol,doublevalue, doublechange){ _symbol=symbol; _value=value; _change=change; } publicdoubleChange{ get{return_change;} } publicstringSymbol{ get{return_symbol;} } publicdoubleValue{ get{return_value;} } } } } 

Like all control designer classes, the StockGridDesigner class overrides the Initialize method to check that it has been correctly associated as a designer for a StockGrid . In addition, StockGridDesigner overrides GetDesignTimeHtml to customize the design-time display of the StockGrid control. This control designer generates a placeholder UI with instructive text indicating that the page developer must first specify a value for the control's StockWebServiceUrl property. Once this property has been set, the designer uses the runtime control's rendering functionality to generate design-time HTML. The designer generates dummy data by using an array of DesignTimeStockObject objects as the data source for the DataGrid contained within StockGrid . This allows the control to have a WYSIWYG display without accessing the Web service at design time, which is desirable and essential because a control does not go through its Pre ­Render phase when used within a designer. As you might recall, StockGrid accesses the Web service it is bound to by overriding OnPreRender .

In addition, StockGridDesigner provides a Toggle View designer verb (or command), which appears on the control's context menu or as a command link in the property browser to allow the page developer to view both customize and normal views of the StockGrid on the design surface.



Developing Microsoft ASP. NET Server Controls and Components
Developing Microsoft ASP.NET Server Controls and Components (Pro-Developer)
ISBN: 0735615829
EAN: 2147483647
Year: 2005
Pages: 183

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net