ASP.NET Web Services

for RuBoard

All this is great, but as a Web developer, I don't want to have to go out and learn HTTP, XML, SOAP, WSDL, and DISCO just so that I can trade with my partners . I don't have the time. ASP.NET to the rescue.

The model for creating XML Web services in ASP.NET is very similar to the model for creating programmable pages. Let's create a very simple Web service and look at what it is composed of. In its simplest form, a Web service is a file with an extension, ASMX, that is new to ASP.NET. As you would expect, no HTML is in this page, only code. Listing 6.3 shows the canonical HelloWorld that in some shape or another tends to be every programmer's first application.

Listing 6.3 A Simple Web Service Saying Hello to the World
 <%@ WebService Language="c#" Class="HelloWorldSvc" %> using System.Web.Services; public class HelloWorldSvc : System.Web.Services.WebService {     [WebMethod]     public string HelloWorld()     {         return "Hello World!";     } } 

That's it! After all the talk of SOAP, XML, and so on, this looks just like a standard class. The .NET framework hides the ugly part of creating XML Web services from you, the developer, allowing you to concentrate on what you need the Web service to do instead of how it does it. Well, the code is cool, but we want to see it do something. Remember that what we just wrote is intended to be called by a program, not by a user. Having to immediately write a test harness just to test a simple Web service is kind of a pain. Consequently, the .NET framework provides a default test harness that will appear if you enter the URL for a Web service endpoint into the browser. If a particular method in a Web service is not specified, it is assumed that the end user needs some more information about the Web service. If I enter http://localhost/book/ webservices /helloworld/HelloWorld.asmx , the address for the Web service in Listing 6.3, I get the browser display shown in Figure 6.2.

Figure 6.2. The automatically created documentation page.

graphics/06fig02.jpg

This page gives some general information about the Web service, including the methods in my Web service. If I click the method name , I get the page shown in Figure 6.3.

Figure 6.3. The automatically created test harness.

graphics/06fig03.jpg

This page gives me a way to invoke the method, and it documents the appropriate ways to call my method using SOAP, HTTP GET, and HTTP POST. If I click the Invoke button, my Web service is called using HTTP GET, and I receive a response back that is shown in Figure 6.4.

Figure 6.4. The XML returned by calling the Web method via HTTP GET.

graphics/06fig04.jpg

One thing shown in Listing 6.3 does look a little unusual, though. The keyword that looks like [WebMethod] is called an attribute in .NET. Attributes are a way for the programmer to declaratively affect the operation of an application without having to write code. This particular attribute does a considerable amount of work. The WebMethod() attribute is somewhat similar to the standard access modifier public . By placing the WebMethod() attribute on my method, I have indicated that I want to make it publicly callable from the outside world. Only functions with WebMethod() are accessible by clients of the Web service. This restriction allows me to continue to have internal methods that I rely on within the class, without having to worry about them being accidentally called by clients . Specifying WebMethod() also tells .NET that it should include this method in the WSDL that it generates for clients. WSDL is the way that clients are going to figure out the proper way to call my methods. To see the WSDL that is automatically generated by .NET, I can call my Web service (shown in Listing 6.3) with this URL: http://localhost/book/webservices/helloworld/helloworld.asmx?WSDL . The output of this is shown in Figure 6.5.

Figure 6.5. The Web Services Description Language (WSDL) output from the Web service.

graphics/06fig05.jpg

NOTE

This URL points to where I placed the sample code on my system and may vary on your system, depending on where you save the source code.


You can see the HelloWorld method clearly delineated in the WSDL. We will take a look at what the WSDL is good for when we show how to consume XML Web services. The WebMethod attribute also provides a way to configure several optional attributes on a Web service.

WebMethodAttribute

The WebMethodAttribute class is what we are using with the WebMethod() attribute mentioned previously. WebMethodAttribute is used to set the options for a Web method.

The BufferResponse property controls how data is sent back to the client from the Web service. The most efficient method for returning data over TCP is to batch it all up and send it in large blocks to the client. This is what is considered buffered mode and is the default both for Web pages and XML Web services. In the case of a large database query, however, it might be nice to start streaming the contents back to the client before the query has finished retrieving all the rows. To do this, set buffer response to false . When buffering is turned off, the results are sent back to the client in 16KB chunks .

The EnableSession property enables session state for a Web service. By default, this attribute is set to false . Think hard about whether you need to enable session state on a Web service, because storing state on the server is going to affect the scalability of your service. However, session state can be utilized with all the attendant options, as discussed in Chapter 4, "State Management and Caching." This includes the State Server and SQL Server modes that are new to ASP.NET.

Listings 6.4 and 6.5 include a sample Web service that provides a Web service “based state service. The service provides two methods, SetValue and GetValue . SetValue enables the user to store some data with a keyname and a value. GetValue enables the user to retrieve data with a keyname. This example also uses the code-behind model (discussed in Chapter 2, "Page Framework") for creating XML Web services. As you can see, the activator for the Web service, the .asmx file, is minimal. In all future examples, I won't even include the .asmx as part of the listing.

Listing 6.4 The Activator .asmx File
 <%@ WebService Language="c#" Codebehind="State.asmx.cs" Class="HelloWorld.State" %> 
Listing 6.5 The Code-Behind File for the Simple State Service
 using System; using System.Collections; using System.ComponentModel; using System.Data; using System.Diagnostics; using System.Web; using System.Web.Services; namespace HelloWorld {     /// <summary>     /// Summary description for State.     /// </summary>     public class State : System.Web.Services.WebService     {         public State()         {             //CODEGEN: This call is required by the ASP.NET Web Services Designer             InitializeComponent();         }         #region Component Designer generated code         /// <summary>         /// Required method for Designer support - do not modify         /// the contents of this method with the code editor.         /// </summary>         private void InitializeComponent()         {         }         #endregion         /// <summary>         /// Clean up any resources being used.         /// </summary>         protected override void Dispose(bool disposing)         {         }         [WebMethod(true)]         public void SetValue(string Name, string Value)         {             Session[Name] = Value;         }         [WebMethod(true)]         public string GetValue(string Name)         {             return Session[Name].ToString();         }     } } 

The Description property supplies a description, which is shown in the Web service help page that is created as an automatic test harness. Listing 6.6 shows the code-behind class for the War Games Web service.

Listing 6.6 A Web Service the That Utilizes the Description Property
 using System; using System.Collections; using System.ComponentModel; using System.Data; using System.Diagnostics; using System.Web; using System.Web.Services; namespace HelloWorld {     public class WarGames : System.Web.Services.WebService     {         protected override void Dispose(bool disposing)         {         }         [WebMethod(Description="List of games")]         public string Games()         {             return "Tic Tac Toe, Chess, Thermonuclear War";         }     } } 

The WebMethodAttribute uses the Description property to indicate what each Web method does. The Description property is set using the syntax for named properties in an attribute. Figure 6.6 shows how the Description property conveniently identifies the Games Web method so we know that it returns a list of games we can play with the WOPR.

Figure 6.6. The test harness page when we hack into War Games.

graphics/06fig06.jpg

The CacheDuration property controls how a Web service is cached. The default for cache duration is 0, meaning that no caching is performed. As mentioned in Chapter 4, "State Management and Caching," huge performance increases can be realized by utilizing caching. The Cache[] object discussed in Chapter 4 is also available in XML Web services. The CacheDuration property is analogous to OutputCaching in a Web page. When this is set to some number of seconds, all output from the Web service is cached for this period of time. Listing 6.7 shows an example of a time service that only updates its output every 60 seconds.

Listing 6.7 The Code Behind Web Service Class That Implements a Cached Time Service
 using System; using System.Collections; using System.ComponentModel; using System.Data; using System.Diagnostics; using System.Web; using System.Web.Services; namespace HelloWorld {     public class Time : System.Web.Services.WebService     {         protected override void Dispose(bool disposing)         {         }         [WebMethod(CacheDuration=60)]         public string GetTime()         {             return DateTime.Now.ToString();         }     } } 

NOTE

If this doesn't appear to work, make sure you are using the test harness outside Visual Studio .NET. Visual Studio .NET performs cache busting that penetrates the cache. The correct behavior will be displayed in Internet Explorer.


The TransactionOption property controls how the Web service interacts with the transaction-processing support found in the common language runtime. By altering the attribute of the Web method, you can control how the method participates in transactions. The default setting for the transaction option is Disabled . This means that by default, a Web method does not participate in any transactions. A Web service is limited to acting as the root in a transaction in version 1.0 of .NET. This limitation means that several of the transaction options provide the same functionality. Required and RequiresNew do the same thing because the Web method must be the root. This possibly could change in future versions. By default, the System.EnterpriseServices assembly is not referenced in VS.NET. To use the TransactionOption enumeration you will need to add a reference.

Serialization

All our samples so far have utilized relatively simple data types. As it turns out, almost any object in .NET can be serialized to XML. This includes Collections, Arrays, and even DataSets. One exception to this rule is any object that is based on System.Collections.Hashtable internally. This includes many of the dictionary-based collections in the base class library with the exception of ListDictionary . Serialization is the process whereby a running object provides a static representation of itself that can be used to later reconstitute this object and create a new running object.

Listing 6.8 shows a Web service that returns an ArrayList of shipping methods.

Listing 6.8 A Web Service That Returns an ArrayList
 using System; using System.Collections; using System.ComponentModel; using System.Data; using System.Diagnostics; using System.Web; using System.Web.Services; namespace NorthwindOrders {     public class collections : System.Web.Services.WebService     {         protected override void Dispose(bool disposing)         {         }         [WebMethod()]         public ArrayList GetShippingMethods()         {             ArrayList al = new ArrayList();             al.Add("UPS Ground");             al.Add("UPS Blue");             al.Add("UPS Red");             al.Add("FedEx Ground");             al.Add("FedEx 2 Day");             return al;         }     } } 

Figure 6.7 shows what the returned XML looks like.

Figure 6.7. The output XML from the Web service that serializes the ArrayList .

graphics/06fig07.jpg

The most interesting data type in my mind, however, is the DataSet. The DataSet is a new feature of ADO.NET that appears to be a perfect data structure for transporting data between XML Web services and client code. A DataSet has schema, which is just like a database. This schema defines the tables, their columns , and the relationship between tables within the DataSet. In this chapter, we aren't going to discuss all the features of DataSets. For more in-depth information on DataSets, see Chapter 11, "Creating Database Applications with ADO.NET."

We are going to look at the ways in which data sets can be used to move data between a Web service and a client. Let's look at a simple case first. The SimpleDataSet example has a single WebMethod Simple() showing in Listing 6.9. This method returns a list of orders in a DataSet. It first builds up the orders by creating a DataSet from scratch. It then creates a DataTable and adds columns to it. Each of these columns is strongly typed. The typeof() operator is used to get a type class to pass to the constructor for the DataColumn. After the DataTable has been created, we add rows to it. Calling NewRow() gives us a new row template whose strong typing is based on the DataTable it came from. Finally, the DataTable is added to the DataSet, and the DataSet is returned to the client. The complete WebMethod can be seen in Listing 6.9.

Listing 6.9 The Simple() WebMethod of SimpleDataSet . It Returns a DataSet Containing a Strongly Typed DataTable
 using System; using System.Collections; using System.ComponentModel; using System.Data; using System.Diagnostics; using System.Web; using System.Web.Services; namespace SimpleDataSet {     public class DataSetSample : System.Web.Services.WebService     {         protected override void Dispose(bool disposing)         {         }         [WebMethod()]         public DataSet Simple()         {             System.Data.DataSet dsOrders = new System.Data.DataSet();             DataTable dt;             // Build a dataset with four columns             dt = new DataTable("Orders");             DataColumn dc = new DataColumn("OrderID", typeof(string));             dt.Columns.Add(dc);             dc = new DataColumn("Date", typeof(string));             dt.Columns.Add(dc);             dc = new DataColumn("Name", typeof(string));             dt.Columns.Add(dc);             dc = new DataColumn("Amount", typeof(decimal));             dt.Columns.Add(dc);             // Populate the dataset             DataRow dr;             dr = dt.NewRow();             dr["OrderID"] = System.Guid.NewGuid();             dr["Date"] = DateTime.Now;             dr["Name"] = "Chris Kinsman";             dr["Amount"] = 123.45;             dt.Rows.Add(dr);             dr = dt.NewRow();             dr["OrderID"] = System.Guid.NewGuid();             dr["Date"] = DateTime.Now.AddDays(1);             dr["Name"] = "Jeffrey McManus";             dr["Amount"] = "234.45";             dt.Rows.Add(dr);             // Add the datatable to the dataset             dsOrders.Tables.Add(dt);             return dsOrders;         }     } } 

Figure 6.8 shows the output from this Web service. It starts with the Schema information for the dataset that we are returning. It defines each of the columns along with the data types. After this section, it uses the predefined schema to represent the data. You can pick out each or the Order rows along with each of the columns data quite easily. It should be quite evident that it would be simple to consume this data in a rigorous fashion.

Figure 6.8. The XML output from SimpleDataSet .

graphics/06fig08.jpg

Let's do a little bit more complex example now. A common data construct is the idea of a Master-Detail relationship. You saw one of these when we were looking at the XML for the invoice. For an order (the master) I had multiple items (the detail). This type of relationship is common in databases, and any method of transferring data must take relationships into account. The example in Listing 6.10 will also return order data; however, this time we will utilize the Northwind database that ships with SQL Server as the source for our data. Listing 6.10 shows the new Web method.

Listing 6.10 A Web Method That Returns a Dataset with a Master Detail Relationship
 [WebMethod()] public DataSet GetOrders(DateTime OrderDate) {     // Setup the connection     SqlConnection cn = new SqlConnection(Application["DSN"].ToString());     // Open the connection     cn.Open();     // Create the orders data adapter     SqlDataAdapter daOrders = new SqlDataAdapter("SELECT * FROM ORDERS WHERE  OrderDate = '" + OrderDate.ToShortDateString() + "'", cn);     // Create the order item data adapter     SqlDataAdapter daOrderDetails = new SqlDataAdapter("SELECT * FROM [Order Details] od, Orders o WHERE o.OrderID = od.OrderID AND o.OrderDate = '" +  OrderDate.ToShortDateString() + "'", cn);     // Create a data set     DataSet ds = new DataSet();     // Get the orders     daOrders.Fill(ds, "Orders");     // Get the order details     daOrderDetails.Fill(ds, "OrderDetails");     // Relate the two on the order id     ds.Relations.Add("OrderID", ds.Tables["Orders"].Columns["OrderID"], ds.Tables["OrderDetails"].Columns["OrderID"]);     // Return the dataset     return ds; } 

This code is somewhat similar to the previous example, but a few differences exist. First, we are using the ADO.NET SqlClient to retrieve the data (for more information on this, see Chapter 11). Second, we are not returning just a single table containing data. We are retrieving all the order and order details for all orders that were placed on OrderDate. The database defines a relationship between these two DataTables on the OrderID column that is present in each of the tables. This yields a DataSet that not only contains the data that matches the criteria from both tables, but also knows about the relationship between the data. Listing 6.11 shows the output when we call the GetOrders WebMethod witha date of a date of 7/8/1996.

Listing 6.11 The XML Output from Calling the GetOrders WebMethod with a Date of 7/8/1996
 <?xml version="1.0" encoding="utf-8" ?> - <DataSet xmlns="http://tempuri.org/"> - <xsd:schema id="NewDataSet" targetNamespace="" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema"  xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> - <xsd:element name="NewDataSet" msdata:IsDataSet="true"> - <xsd:complexType> - <xsd:choice maxOccurs="unbounded"> - <xsd:element name="Orders"> - <xsd:complexType> - <xsd:sequence>   <xsd:element name="OrderID" type="xsd:int" minOccurs="0" />   <xsd:element name="CustomerID" type="xsd:string" minOccurs="0" />   <xsd:element name="EmployeeID" type="xsd:int" minOccurs="0" />   <xsd:element name="OrderDate" type="xsd:dateTime" minOccurs="0" />   <xsd:element name="RequiredDate" type="xsd:dateTime" minOccurs="0" />   <xsd:element name="ShippedDate" type="xsd:dateTime" minOccurs="0" />   <xsd:element name="ShipVia" type="xsd:int" minOccurs="0" />   <xsd:element name="Freight" type="xsd:decimal" minOccurs="0" />   <xsd:element name="ShipName" type="xsd:string" minOccurs="0" />   <xsd:element name="ShipAddress" type="xsd:string" minOccurs="0" />   <xsd:element name="ShipCity" type="xsd:string" minOccurs="0" />   <xsd:element name="ShipRegion" type="xsd:string" minOccurs="0" />   <xsd:element name="ShipPostalCode" type="xsd:string" minOccurs="0" />   <xsd:element name="ShipCountry" type="xsd:string" minOccurs="0" />   </xsd:sequence>   </xsd:complexType>   </xsd:element> - <xsd:element name="OrderDetails"> - <xsd:complexType> - <xsd:sequence>   <xsd:element name="OrderID" type="xsd:int" minOccurs="0" />   <xsd:element name="ProductID" type="xsd:int" minOccurs="0" />   <xsd:element name="UnitPrice" type="xsd:decimal" minOccurs="0" />   <xsd:element name="Quantity" type="xsd:short" minOccurs="0" />   <xsd:element name="Discount" type="xsd:float" minOccurs="0" />   <xsd:element name="OrderID1" type="xsd:int" minOccurs="0" />   <xsd:element name="CustomerID" type="xsd:string" minOccurs="0" />   <xsd:element name="EmployeeID" type="xsd:int" minOccurs="0" />   <xsd:element name="OrderDate" type="xsd:dateTime" minOccurs="0" />   <xsd:element name="RequiredDate" type="xsd:dateTime" minOccurs="0" />   <xsd:element name="ShippedDate" type="xsd:dateTime" minOccurs="0" />   <xsd:element name="ShipVia" type="xsd:int" minOccurs="0" />   <xsd:element name="Freight" type="xsd:decimal" minOccurs="0" />   <xsd:element name="ShipName" type="xsd:string" minOccurs="0" />   <xsd:element name="ShipAddress" type="xsd:string" minOccurs="0" />   <xsd:element name="ShipCity" type="xsd:string" minOccurs="0" />   <xsd:element name="ShipRegion" type="xsd:string" minOccurs="0" />   <xsd:element name="ShipPostalCode" type="xsd:string" minOccurs="0" />   <xsd:element name="ShipCountry" type="xsd:string" minOccurs="0" />   </xsd:sequence>   </xsd:complexType>   </xsd:element>   </xsd:choice>   </xsd:complexType> - <xsd:unique name="Constraint1">   <xsd:selector xpath=".//Orders" />   <xsd:field xpath="OrderID" />   </xsd:unique> - <xsd:keyref name="OrderID" refer="Constraint1">   <xsd:selector xpath=".//OrderDetails" />   <xsd:field xpath="OrderID" />   </xsd:keyref>   </xsd:element>   </xsd:schema> - <diffgr:diffgram xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" xmlns:diffgr="urn:schemas-microsoft-com:xml-diffgram-v1"> - <NewDataSet xmlns=""> - <Orders diffgr:id="Orders1" msdata:rowOrder="0">   <OrderID>10250</OrderID>   <CustomerID>HANAR</CustomerID>   <EmployeeID>4</EmployeeID>   <OrderDate>1996-07-08T00:00:00.0000000-07:00</OrderDate>   <RequiredDate>1996-08-05T00:00:00.0000000-07:00</RequiredDate>   <ShippedDate>1996-07-12T00:00:00.0000000-07:00</ShippedDate>   <ShipVia>2</ShipVia>   <Freight>65.83</Freight>   <ShipName>Hanari Carnes</ShipName>   <ShipAddress>Rua do Pao, 67</ShipAddress>   <ShipCity>Rio de Janeiro</ShipCity>   <ShipRegion>RJ</ShipRegion>   <ShipPostalCode>05454-876</ShipPostalCode>   <ShipCountry>Brazil</ShipCountry>   </Orders> - <Orders diffgr:id="Orders2" msdata:rowOrder="1">   <OrderID>10251</OrderID>   <CustomerID>VICTE</CustomerID>   <EmployeeID>3</EmployeeID>   <OrderDate>1996-07-08T00:00:00.0000000-07:00</OrderDate>   <RequiredDate>1996-08-22T00:00:00.0000000-07:00</RequiredDate>   <ShippedDate>1996-07-15T00:00:00.0000000-07:00</ShippedDate>   <ShipVia>1</ShipVia>   <Freight>41.5</Freight>   <ShipName>Victuailles en stock</ShipName>   <ShipAddress>2, rue du Commerce</ShipAddress>   <ShipCity>Lyon</ShipCity>   <ShipPostalCode>69004</ShipPostalCode>   <ShipCountry>France</ShipCountry>   </Orders> - <OrderDetails diffgr:id="OrderDetails1" msdata:rowOrder="0">   <OrderID>10250</OrderID>   <ProductID>41</ProductID>   <UnitPrice>7.7</UnitPrice>   <Quantity>10</Quantity>   <Discount>0</Discount>   <OrderID1>10250</OrderID1>   <CustomerID>HANAR</CustomerID>   <EmployeeID>4</EmployeeID>   <OrderDate>1996-07-08T00:00:00.0000000-07:00</OrderDate>   <RequiredDate>1996-08-05T00:00:00.0000000-07:00</RequiredDate>   <ShippedDate>1996-07-12T00:00:00.0000000-07:00</ShippedDate>   <ShipVia>2</ShipVia>   <Freight>65.83</Freight>   <ShipName>Hanari Carnes</ShipName>   <ShipAddress>Rua do Pao, 67</ShipAddress>   <ShipCity>Rio de Janeiro</ShipCity>   <ShipRegion>RJ</ShipRegion>   <ShipPostalCode>05454-876</ShipPostalCode>   <ShipCountry>Brazil</ShipCountry>   </OrderDetails> - <OrderDetails diffgr:id="OrderDetails2" msdata:rowOrder="1">   <OrderID>10250</OrderID>   <ProductID>51</ProductID>   <UnitPrice>42.4</UnitPrice>   <Quantity>35</Quantity>   <Discount>0.15</Discount>   <OrderID1>10250</OrderID1>   <CustomerID>HANAR</CustomerID>   <EmployeeID>4</EmployeeID>   <OrderDate>1996-07-08T00:00:00.0000000-07:00</OrderDate>   <RequiredDate>1996-08-05T00:00:00.0000000-07:00</RequiredDate>   <ShippedDate>1996-07-12T00:00:00.0000000-07:00</ShippedDate>   <ShipVia>2</ShipVia>   <Freight>65.83</Freight>   <ShipName>Hanari Carnes</ShipName>   <ShipAddress>Rua do Pao, 67</ShipAddress>   <ShipCity>Rio de Janeiro</ShipCity>   <ShipRegion>RJ</ShipRegion>   <ShipPostalCode>05454-876</ShipPostalCode>   <ShipCountry>Brazil</ShipCountry>   </OrderDetails> - <OrderDetails diffgr:id="OrderDetails3" msdata:rowOrder="2">   <OrderID>10250</OrderID>   <ProductID>65</ProductID>   <UnitPrice>16.8</UnitPrice>   <Quantity>15</Quantity>   <Discount>0.15</Discount>   <OrderID1>10250</OrderID1>   <CustomerID>HANAR</CustomerID>   <EmployeeID>4</EmployeeID>   <OrderDate>1996-07-08T00:00:00.0000000-07:00</OrderDate>   <RequiredDate>1996-08-05T00:00:00.0000000-07:00</RequiredDate>   <ShippedDate>1996-07-12T00:00:00.0000000-07:00</ShippedDate>   <ShipVia>2</ShipVia>   <Freight>65.83</Freight>   <ShipName>Hanari Carnes</ShipName>   <ShipAddress>Rua do Pao, 67</ShipAddress>   <ShipCity>Rio de Janeiro</ShipCity>   <ShipRegion>RJ</ShipRegion>   <ShipPostalCode>05454-876</ShipPostalCode>   <ShipCountry>Brazil</ShipCountry>   </OrderDetails> - <OrderDetails diffgr:id="OrderDetails4" msdata:rowOrder="3">   <OrderID>10251</OrderID>   <ProductID>22</ProductID>   <UnitPrice>16.8</UnitPrice>   <Quantity>6</Quantity>   <Discount>0.05</Discount>   <OrderID1>10251</OrderID1>   <CustomerID>VICTE</CustomerID>   <EmployeeID>3</EmployeeID>   <OrderDate>1996-07-08T00:00:00.0000000-07:00</OrderDate>   <RequiredDate>1996-08-22T00:00:00.0000000-07:00</RequiredDate>   <ShippedDate>1996-07-15T00:00:00.0000000-07:00</ShippedDate>   <ShipVia>1</ShipVia>   <Freight>41.5</Freight>   <ShipName>Victuailles en stock</ShipName>   <ShipAddress>2, rue du Commerce</ShipAddress>   <ShipCity>Lyon</ShipCity>   <ShipPostalCode>69004</ShipPostalCode>   <ShipCountry>France</ShipCountry>   </OrderDetails> - <OrderDetails diffgr:id="OrderDetails5" msdata:rowOrder="4">   <OrderID>10251</OrderID>   <ProductID>57</ProductID>   <UnitPrice>15.6</UnitPrice>   <Quantity>15</Quantity>   <Discount>0.05</Discount>   <OrderID1>10251</OrderID1>   <CustomerID>VICTE</CustomerID>   <EmployeeID>3</EmployeeID>   <OrderDate>1996-07-08T00:00:00.0000000-07:00</OrderDate>   <RequiredDate>1996-08-22T00:00:00.0000000-07:00</RequiredDate>   <ShippedDate>1996-07-15T00:00:00.0000000-07:00</ShippedDate>   <ShipVia>1</ShipVia>   <Freight>41.5</Freight>   <ShipName>Victuailles en stock</ShipName>   <ShipAddress>2, rue du Commerce</ShipAddress>   <ShipCity>Lyon</ShipCity>   <ShipPostalCode>69004</ShipPostalCode>   <ShipCountry>France</ShipCountry>   </OrderDetails> - <OrderDetails diffgr:id="OrderDetails6" msdata:rowOrder="5">   <OrderID>10251</OrderID>   <ProductID>65</ProductID>   <UnitPrice>16.8</UnitPrice>   <Quantity>20</Quantity>   <Discount>0</Discount>   <OrderID1>10251</OrderID1>   <CustomerID>VICTE</CustomerID>   <EmployeeID>3</EmployeeID>   <OrderDate>1996-07-08T00:00:00.0000000-07:00</OrderDate>   <RequiredDate>1996-08-22T00:00:00.0000000-07:00</RequiredDate>   <ShippedDate>1996-07-15T00:00:00.0000000-07:00</ShippedDate>   <ShipVia>1</ShipVia>   <Freight>41.5</Freight>   <ShipName>Victuailles en stock</ShipName>   <ShipAddress>2, rue du Commerce</ShipAddress>   <ShipCity>Lyon</ShipCity>   <ShipPostalCode>69004</ShipPostalCode>   <ShipCountry>France</ShipCountry>   </OrderDetails>   </NewDataSet>   </diffgr:diffgram>   </DataSet> 

Dig into the XML and look for the tag "- <xsd:unique name="Constraint1">" . This starts the section that defines the relationship between the tables. It says that a constraint named Constraint1 defines the relationship between the DataTable named Orders and the DataTable named OrderDetails. The relationship is on a field named OrderID in each DataTable.

for RuBoard


C# Developer[ap]s Guide to ASP. NET, XML, and ADO. NET
C# Developer[ap]s Guide to ASP. NET, XML, and ADO. NET
ISBN: 672321556
EAN: N/A
Year: 2005
Pages: 103

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