Connecting Clients to a Web Service

When a Web service is available, clients will want to connect and use it. Behind the scenes, ASP.NET Web Services emit a WSDL file, which is an XML file for describing the capabilities of a Web service. WSDL is also structured so that automated tools, such as C#Builder, can read it and create proxy objects for clients to communicate with the Web service. Because proxies are created automatically, it saves the developer from having to work with the underlying plumbing of Web Services protocols such as WSDL. If you're interested, you can view the WSDL for the DataService Web service by clicking the Service Description link on the Web Service helper page. This section moves away from the Web Service helper page and shows how to create a client application that communicates with a Web service.

When using the Web service, client code just connects to the proxy that was made by creating a reference to the Web service. The client communicates with the proxy, and can use it to call methods on the Web service. In this example the client is an ASP.NET application that works with data in a DataGrid control. This DataGrid will be configured to insert, modify, and delete products.

To set up the client, create a new ASP.NET Web Application in the same project group as the Web service and name it DataClient. It doesn't have to be in the same project group, but that's how I organized it to work with both projects at the same time. Drag a DataGrid to the designer surface, open the Property Builder (accessible from the DataGrid context menu or the Object Inspector link), and go to the Columns page (see Figure 17.4).

Figure 17.4. The Columns screen of the Property Builder editor.

graphics/17fig04.jpg

Uncheck the Create Columns Automatically at Run Time box. In the Available Columns list, expand the Button Column branch, select the Edit, Update, Cancel node, and add it to the Selected Columns list by clicking the greater than (arrow) button. Don't make any changes to the EditCommandColumn properties. Next, add a Delete button the same way.

After editing buttons have been added, move up the tree in the Available Columns list, select Bound Column, and add three Bound Column entries in the Selected Columns list. For each Bound column in the Selected Columns list, perform the following modifications in the Bound Column properties: Set Header Text to "Product Name" and Data Field to "ProductName" for the first Bound column, Header Text to "Quantity Per Unit" and Data Field to "QuantityPerUnit" for the second Bound column, and Header Text to "Unit Price" and Data Field to "UnitPrice" for the third Bound column. After configuring columns, close the Property Builder editor, open the Auto Format editor, and select Classic 2 to make the DataGrid look a little better.

Next, the DataGrid needs access to the Products table data. This will be accomplished by creating a reference to the Web service, calling the GetProducts Web method, and binding the DataGrid to the returned DataSet. To accomplish this, find the References branch under the DataClient project in the Project Manager, right-click, and select Add Web Reference from its context menu. In the address bar of the Borland UDDI Directory page (see Figure 17.5) that appears, type http://localhost/DataService/DataService.asmx into the address bar. This is the address of the DataService Web service on the local machine. If the Web service is physically located elsewhere, that URL would need to be entered into the address bar. The other links on the Borland UDDI Directory page may be followed to find other available Web services.

Figure 17.5. The Borland UDDI directory page.

graphics/17fig05.jpg

After the address is added, click the GO arrow to the right of the address bar, which will bring up a page with Web methods from the DataService Web service. If an error occurs, double-check the address to make them sure it has been entered properly. Click the Add Reference button to add the reference to the DataClient project. This creates a managed proxy that may be called to execute Web Service methods.

The proxy that is created appears in a new branch named Web References in the DataClient project in the Project Manager. Open the tree under Web References and Reference.map and open the References.cs file. The following code shows a snippet from this file that is relevant to this example:

 // // This source code was auto-generated by wsdl, Version=1.1.4322.573. // namespace DataClient {     using System.Diagnostics;     using System.Xml.Serialization;     using System;     using System.Web.Services.Protocols;     using System.ComponentModel;     using System.Web.Services;     /// <remarks/>     [System.Diagnostics.DebuggerStepThroughAttribute()]     [System.ComponentModel.DesignerCategoryAttribute("code")]     [System.Web.Services.WebServiceBindingAttribute        (Name="ProductsTableServiceSoap",        Namespace="http://www.samspublishing.com/")]     public class ProductsTableService :        System.Web.Services.Protocols.SoapHttpClientProtocol {     {        // code removed for clarity     } } 

The preceding code shows that the proxy was created as a member of the DataClient namespace, which is the same namespace as the DataClient Web Form. Just to be clear, the Web Form is named DataClient and its namespace is also DataClient. Notice that the type name is ProductsTableService, rather than DataService. Back in the DataService code-behind file, I added a WebService attribute to the DataService class with a Name attribute of ProductsTableService. This WebService attribute was picked up when the WSDL for the Web service was generated, which was read when the Web Reference proxy was generated, resulting in the proxy type name of ProductsTableService. Had the WebService attribute not had a Name attribute or not been defined at all, the proxy type name would have been DataService, which is the name of the Web Service type in the DataService project.

Because the namespace for the proxy, DataClient, is the same as the Web Form with the DataGrid, the proxy can be referenced directly in the code-behind file. The process required is to create an instance of the proxy, use the reference created to call the GetProducts method, and bind the resulting DataSet to the DataGrid. The following code is the Page_Load method from the code-behind of the DataClient Web Form for performing these tasks:

 private void Page_Load(object sender, System.EventArgs e) {    if (!IsPostBack)    {       LoadData();    } } private void LoadData() {    ProductsTableService pts = new ProductsTableService();    DataSet ds = pts.GetProducts();    dataGrid1.DataSource = ds;    dataGrid1.DataBind(); } 

The ProductsTableService is the type identifier of the proxy. Notice how it is used and referenced just like any other type. All the XML and other Web Services protocol plumbing is taken care of by the ASP.NET infrastructure. Do you recall the XML file with the schema and data and other cryptic information that appeared when invoking the GetProducts method on the Web Services helper page? ASP.NET will intercept the XML, deserialize it into a DataSet type, and return the DataSet to the calling program.

By default, the authentication mode in web.config is set to Windows authentication. Set this to "None" in the web.config file of both the client and the Web service to avoid security errors when trying to communicate with the Web service:

 <authentication mode="None" /> 

After the security is set, run the DataClient Web Form application and observe that the DataGrid appears with Products table data. The complete program is shown in Listing 17.5 and Listing 17.6, including editing logic.

Listing 17.5 HTML File for an ASP.NET Client Application Consuming a Web Service (DataClient.aspx)
 <%@ Page    language="c#"    Debug="true"    Codebehind="DataClient.aspx.cs"    AutoEventWireup="false"    Inherits="DataClient.DataClient"    SmartNavigation="true" %> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> <html>   <head>     <title></title>     <meta name="GENERATOR"        content="Borland ASP.NET Designer for c# Package Library 7.1">   </head>   <body ms_positioning="FlowLayout">   <form name=PageBottom runat="server">     <p>       <asp:button id=btnInsert runat="server" text="Insert New Record">       </asp:button>     </p>     <p>       <asp:datagrid id=dgrProducts runat="server" autogeneratecolumns="False"                     forecolor="Black" cellpadding="4" backcolor="White"                     bordercolor="#DEDFDE" borderwidth="1px"                     gridlines="Vertical" borderstyle="None">         <footerstyle backcolor="#CCCC99">         </footerstyle>         <headerstyle font-bold="True" forecolor="White" backcolor="#6B696B">         </headerstyle>         <pagerstyle horizontalalign="Right" forecolor="Black"                     backcolor="#F7F7DE" mode="NumericPages">         </pagerstyle>         <selecteditemstyle font-bold="True" forecolor="White"                            backcolor="#CE5D5A">         </selecteditemstyle>         <alternatingitemstyle backcolor="White">         </alternatingitemstyle>         <itemstyle backcolor="#F7F7DE">         </itemstyle>         <columns>           <asp:editcommandcolumn buttontype="LinkButton" updatetext="Update"                                  canceltext="Cancel" edittext="Edit">           </asp:editcommandcolumn>           <asp:buttoncolumn text="Delete" commandname="Delete">           </asp:buttoncolumn>           <asp:boundcolumn datafield="ProductName" headertext="Product Name">           </asp:boundcolumn>           <asp:boundcolumn datafield="QuantityPerUnit"                            headertext="Quantity Per Unit">           </asp:boundcolumn>           <asp:boundcolumn datafield="UnitPrice" headertext="Unit Price">           </asp:boundcolumn>         </columns>       </asp:datagrid>     </p>     <asp:label id=lblMessage runat="server" forecolor="Red" font-bold="True">     </asp:label>   </form> </body> </html> 
Listing 17.6 Code-Behind File for an ASP.NET Client Application Consuming a Web Service (DataClient.aspx.cs)
 using System; using System.Collections; using System.ComponentModel; using System.Data; using System.Drawing; using System.Web; using System.Web.Services.Protocols; using System.Web.SessionState; using System.Web.UI; using System.Web.UI.WebControls; using System.Web.UI.HtmlControls; namespace DataClient {    public class DataClient : System.Web.UI.Page    {       protected System.Web.UI.WebControls.Button btnInsert;       protected System.Web.UI.WebControls.DataGrid dgrProducts;       protected System.Web.UI.WebControls.Label lblMessage;       private void Page_Load(object sender, System.EventArgs e)       {          if (!IsPostBack)          {             LoadData();          }       }       private void LoadData()       {          ProductsTableService pts = new ProductsTableService();          DataSet ds = pts.GetProducts();          dgrProducts.DataSource = ds;          dgrProducts.DataBind();       }       #region Web Form Designer generated code       override protected void OnInit(EventArgs e)       {          //          // CODEGEN: This call is required by the          // ASP.NET Web Form Designer.          //          InitializeComponent();          base.OnInit(e);       }       /// <summary>       /// Required method for Designer support - do not modify       /// the contents of this method with the code editor.       /// </summary>       private void InitializeComponent()       {          this.btnInsert.Click +=             new System.EventHandler(this.btnInsert_Click);          this.dgrProducts.CancelCommand +=             new System.Web.UI.WebControls.DataGridCommandEventHandler(                this.dgrProducts_CancelCommand);          this.dgrProducts.EditCommand +=             new System.Web.UI.WebControls.DataGridCommandEventHandler(                this.dgrProducts_EditCommand);          this.dgrProducts.UpdateCommand +=             new System.Web.UI.WebControls.DataGridCommandEventHandler(                this.dgrProducts_UpdateCommand);          this.dgrProducts.DeleteCommand +=             new System.Web.UI.WebControls.DataGridCommandEventHandler(                this.dgrProducts_DeleteCommand);          this.Load += new System.EventHandler(this.Page_Load);       }       #endregion       private void btnInsert_Click(object sender, System.EventArgs e)       {          ProductsTableService pts = new ProductsTableService();          pts.InsertProduct("New", "1", 0.00f);          LoadData();       }       private void dgrProducts_EditCommand(          object source,          System.Web.UI.WebControls.DataGridCommandEventArgs e)       {          dgrProducts.EditItemIndex = e.Item.ItemIndex;          Session["ProductName"]     = e.Item.Cells[2].Text;          Session["QuantityPerUnit"] = e.Item.Cells[3].Text;          Session["UnitPrice"]       = e.Item.Cells[4].Text;          LoadData();       }       private void dgrProducts_CancelCommand(          object source,          System.Web.UI.WebControls.DataGridCommandEventArgs e)       {          dgrProducts.EditItemIndex = -1;          LoadData();       }       private void dgrProducts_DeleteCommand(          object source,          System.Web.UI.WebControls.DataGridCommandEventArgs e)       {          string productName     = e.Item.Cells[2].Text;          string quantityPerUnit = e.Item.Cells[3].Text;          float  unitPrice       = Single.Parse(e.Item.Cells[4].Text);          ProductsTableService pts = new ProductsTableService();          pts.DeleteProduct(productName, quantityPerUnit, unitPrice);          dgrProducts.EditItemIndex = -1;          LoadData();       }       private void dgrProducts_UpdateCommand(          object source,          System.Web.UI.WebControls.DataGridCommandEventArgs e)       {          try          {             TextBox txtProdName = (TextBox)e.Item.Cells[2].Controls[0];             TextBox txtUnPrice  = (TextBox)e.Item.Cells[4].Controls[0];             string newProductName     =                txtProdName.Text;             string newQuantityPerUnit =                txtQtyUnit.Text;             float  newUnitPrice       =                Single.Parse(txtUnPrice.Text);             string oldProductName     =               (string)Session["ProductName"];             string oldQuantityPerUnit =               (string)Session["QuantityPerUnit"];             float  oldUnitPrice       =               Single.Parse((string)Session["UnitPrice"]);             ProductsTableService pts = new ProductsTableService();             pts.UpdateProduct(                newProductName, newQuantityPerUnit, newUnitPrice,                oldProductName, oldQuantityPerUnit, oldUnitPrice);          }          catch (Exception ex)          {             lblMessage.Text = ex.ToString();          }          finally          {             dgrProducts.EditItemIndex = -1;             LoadData();          }       }    } } 

When binding in the Page_Load method, remember to check for the postback, as done in Listing 17.6. Failure to do so will result in updates not behaving correctly. Listing 17.6 also contains methods that assist in editing data in the DataGrid.

Each of the DataGrid editing commands extracts information from the DataGrid, creates an instance of the Web Service proxy, and invokes its appropriate command. The event handlers were created by selecting the DataGrid in the designer surface, opening the Events tab of the Object Inspector, and double-clicking the value portion of the DeleteCommand, EditCommand, and UpdateCommand events.

The e.item.Cells references the column cell of the current item in the DataGrid being worked on. The first and second cells of each column, index 0 and 1 respectively, hold the link buttons, so e.item.Cells[2] is the first column with database data. The statements in the dgrProducts_UpdateCommand method need to reference e.item.Cells[2].Controls[0]. The reason is that when the row is in edit mode, each value resides in a TextBox, which is the first control in the cell, rather than just the cell. Because all information in the DataGrid is managed as strings, the Single.Parse method is required to convert the string value of UnitPrice to float.

To indicate which row is currently being edited, the dgrProducts_EditCommand method sets the EditItemIndex of the DataGrid to the row number that was selected for editing. The other events set EditItemIndex to -1 to indicate that no record is being edited. The dgrProducts_EditCommand method also extracts current values from the row being edited. The purpose of this is so that these values can be obtained by the dgrProducts_UpdateCommand method to use as old values. If this were a Windows Forms application, these values would have been saved in type member fields. However, recall that ASP.NET Web Forms are stateless in nature, which requires use of Session state to maintain a caller's state between method calls. During Web Form page processing, after each event method has completed, the LoadData method is called to ensure the DataGrid shows the latest changes. Figure 17.6 shows what the DataGrid looks like on a running Web Form when populated with a DataSet retrieved from the DataService Web service.

Figure 17.6. The DataGrid control running on a Web Form with data supplied by a Web service.

graphics/17fig06.jpg

SHOP TALK
XML WEB SERVICES FOR TODAY AND THE FUTURE

XML Web services are a relatively new technology with a lot of existing capability and future promise. Because of this, many people and companies are trying to figure out how to use them and what the benefits are. As with many new technologies, there will be some learning. Microsoft's first try at Web services was My Services, a set of services, such as address book, calendar, and favorites list that people could use over the Internet at the time and place of their choosing. For various reasons, it just didn't work. However, one day a Web service like My Services may work and become such a convenience that we will wonder what we ever did without it.

When considering how to use Web services there are three primary arenas: inside your business (EAI), with other businesses (B2B), or with consumers (B2C). Because of the Microsoft experience, it would be wise to do much research before embarking on a B2C implementation of Web Services, which encompasses the most risk. An emerging model of Web Service usage is in the B2B arena. Companies such as Amazon and Google are enjoying success in allowing subscribers to access their Web services. I even use Amazon.com Web services on my site, C# Station, for displaying a list of all the C# books they sell. It is only a matter of time before I begin using Google Web services. In the case of Amazon.com, the Web services generate cash through direct sales. For Google, Web services drive traffic, which leads to other business goals. The most immediately profitable implementation of Web Services is often for EAI, getting your systems to talk to each other. Web services are based on open standards and are easy to implement. This leads to cost savings by not having to implement new interfaces every time another branch of the company needs access to the information or services of your application.

Looking to the future, XML Web services are evolving. The services that exist today with SOAP, WSDL, and XML are simply the beginning. The Web Services Interoperability Organization (WS-I) is an multi-corporate organization designed to promote interoperability and standardization among Web Service implementations. Currently they're building new standards, such as WS-Security, WS-Routing, and WS-Addressing, which build upon existing Web Services standards and form the foundation for future building blocks in standardization. You can build Web services today, but you should also look to the future as we move forward with automating the Internet.



C# Builder KickStart
C# Builder KickStart
ISBN: 672325896
EAN: N/A
Year: 2003
Pages: 165

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