When building the WCF services so far, the data contract that was defined depended upon simple types or primitive datatypes. In the case of the earlier WCF service, a .NET type of Integer was exposed, which in turn was mapped to an XSD type of int. You might not have seen the input and output types actually defined in the WSDL document that was provided via the WCF-generated one, but they are there. These types are actually exposed through an imported .xsd document (a dynamic document). This bit of the WSDL document is presented here:
<wsdl:types> <xsd:schema targetNamespace="http://tempuri.org/Imports"> <xsd:import schemaLocation="http://localhost:8000/docs?xsd=xsd0" namespace="http://tempuri.org/" /> <xsd:import schemaLocation="http://localhost:8000/docs?xsd=xsd1" namespace="http://schemas.microsoft.com/2003/10/Serialization/" /> </xsd:schema> </wsdl:types>
Typing in the XSD location of http://localhost:8000/docs?xsd=xsd0 gives you the input and output parameters of the service. For instance, looking at the definition of the Add() method, you will see the following bit of code:
<xs:element name="Add"> <xs:complexType> <xs:sequence> <xs:element minOccurs="0" name="a" type="xs:int" /> <xs:element minOccurs="0" name="b" type="xs:int" /> </xs:sequence> </xs:complexType> </xs:element> <xs:element name="AddResponse"> <xs:complexType> <xs:sequence> <xs:element minOccurs="0" name="AddResult" type="xs:int" /> </xs:sequence> </xs:complexType> </xs:element>
This bit of XML code indicates that there are two required input parameters (a and b) that are of type int; in return, the consumer gets an element called <AddResult>, which contains a value of type int.
As a builder of this WCF service, you didn’t have to build the data contract, mainly because this service used simple types. When using complex types, you have to create a data contract in addition to your service contract.
For an example of working with data contracts, create a new WCF service (again within a Console Application project) called WCF_WithDataContract. In this case, you still need an interface that defines your service contract, and then another class that implements that interface. In addition to these items, you also need another class that defines the data contract.
Like the service contract, which makes use of the <ServiceContract()> and the <OperationContract()> attributes, the data contract uses the <DataContract()> and <DataMember()> attributes. To gain access to these attributes, you have to make a reference to the System.Runtime.Serialization namespace in your project and import this namespace into the file.
The full WCF definition is presented here:
Imports System Imports System.ServiceModel Imports System.Runtime.Serialization <DataContract()> _ Public Class Customer <DataMember()> _ Public FirstName As String <DataMember()> _ Public LastName As String End Class <ServiceContract()> _ Public Interface IHelloCustomer <OperationContract()> _ Function HelloFirstName(ByVal cust As Customer) As String <OperationContract()> _ Function HelloFullName(ByVal cust As Customer) As String End Interface Public Class HelloCustomer Implements IHelloCustomer Public Function HelloFirstName(ByVal cust As Customer) As String _ Implements IHelloCustomer.HelloFirstName Return "Hello " & cust.FirstName End Function Public Function HelloFullName(ByVal cust As Customer) As String _ Implements IHelloCustomer.HelloFullName Return "Hello " & cust.FirstName & " " & cust.LastName End Function End Class
Here, you can see that the System.Runtime.Serialization namespace is also imported, and the first class in the file is the data contract of the service. This class, the Customer class, has two members: FirstName and LastName. Both of these properties are of type String. You specify a class as a data contract through the use of the <DataContract()> attribute:
<DataContract()> _ Public Class Customer ' Code removed for clarity End Class
Now, any of the properties contained in the class are also part of the data contract through the use of the <DataMember()> attribute:
<DataContract()> _ Public Class Customer <DataMember()> _ Public FirstName As String <DataMember()> _ Public LastName As String End Class
Finally, the Customer object is used in the interface as well as the class that implements the IHelloCustomer interface.
The next step is the same as before: Change the Module1.vb file so that it becomes the host of the WCF service you just built. This task is illustrated in the following example:
Imports System Imports System.ServiceModel Imports System.ServiceModel.Description Module Module1 Sub Main() Using serviceHost As ServiceHost = New ServiceHost(GetType(HelloCustomer)) Dim ntb As NetTcpBinding = New NetTcpBinding(SecurityMode.None) serviceHost.AddServiceEndpoint(GetType(IHelloCustomer), ntb, _ New Uri("net.tcp://192.168.1.102:8080/HelloCustomer/")) Dim smb As New ServiceMetadataBehavior() smb.HttpGetEnabled = True smb.HttpGetUrl = New Uri("http://localhost:8000/docs") serviceHost.Description.Behaviors.Add(smb) serviceHost.Open() Console.WriteLine("Press the <ENTER> key to close the host.") Console.ReadLine() End Using End Sub End Module
This host uses the IHelloCustomer interface and builds an endpoint at net.tcp://192.168.1.102:8080/HelloCustomer. Next, let’s look at consuming this service from another console application.
Now that the service is running and in place, the next step is to build the consumer. To begin, build a new console application from Visual Studio 2005 and call the project HelloWorldConsumer. Again, right-click on the solution and select Add Service Reference from the options provided in the menu.
From the Add Service Reference dialog, add http://localhost:8000/docs as the service URI and HelloCustomerService as the service reference name, as shown in Figure 30-11.
Figure 30-11
This will add the changes to the references and the app.config file just as before, enabling you to consume the service. The following code shows what is required:
Module Module1 Sub Main() Dim svc As New HelloCustomerService.HelloCustomerClient() Dim cust As New HelloCustomerService.Customer() Dim result As String svc.Open() Console.WriteLine("What is your first name?") cust.FirstName = Console.ReadLine() Console.WriteLine("What is your last name?") cust.LastName = Console.ReadLine() result = svc.HelloFullName(cust) svc.Close() Console.WriteLine(result) Console.ReadLine() End Sub End Module
As a consumer, once you make the reference, you will notice that the service reference doesn’t just provide a HelloCustomerClient object; you will also find the Customer object that was defined through the service’s data contract.
Therefore, the preceding code block just instantiates both of these objects and builds the Customer object before it is passed into the HelloFullName() method provided by the service. Running this bit of code will return the results shown in Figure 30-12.
Figure 30-12
When you make a reference to the HelloCustomer service, looking at the WSDL you will find the following XSD imports:
<wsdl:types> <xsd:schema targetNamespace="http://tempuri.org/Imports"> <xsd:import schemaLocation="http://localhost:8000/docs?xsd=xsd0" namespace="http://tempuri.org/" /> <xsd:import schemaLocation="http://localhost:8000/docs?xsd=xsd1" namespace="http://schemas.microsoft.com/2003/10/Serialization/" /> <xsd:import schemaLocation="http://localhost:8000/docs?xsd=xsd2" namespace="http://schemas.datacontract.org/2004/07/WCF_WithDataContract" /> </xsd:schema> </wsdl:types>
http://localhost:8000/docs?xsd=xsd2 provides the details on your Customer object. The code from this file is shown here:
<?xml version="1.0" encoding="utf-8" ?> <xs:schema elementFormDefault="qualified" targetNamespace="http://schemas.datacontract.org/2004/07/WCF_WithDataContract" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:tns="http://schemas.datacontract.org/2004/07/WCF_WithDataContract"> <xs:complexType name="Customer"> <xs:sequence> <xs:element minOccurs="0" name="FirstName" nillable="true" type="xs:string" /> <xs:element minOccurs="0" name="LastName" nillable="true" type="xs:string" /> </xs:sequence> </xs:complexType> <xs:element name="Customer" nillable="true" type="tns:Customer" /> </xs:schema>
This is an XSD description of the Customer object. Making a reference to the WSDL that includes the XSD description of the Customer object causes the auto-generated proxy class to create the following class as part of the proxy:
<System.CodeDom.Compiler.GeneratedCodeAttribute("System.Runtime.Serialization", _ "3.0.0.0"), _ System.Runtime.Serialization.DataContractAttribute( _ [Namespace]:="http://schemas.datacontract.org/2004/07/WCF_WithDataContract"), _ System.SerializableAttribute()> _ Partial Public Class Customer Inherits Object Implements System.Runtime.Serialization.IExtensibleDataObject <System.NonSerializedAttribute()> _ Private extensionDataField As _ System.Runtime.Serialization.ExtensionDataObject <System.Runtime.Serialization.OptionalFieldAttribute()> _ Private FirstNameField As String <System.Runtime.Serialization.OptionalFieldAttribute()> _ Private LastNameField As String Public Property ExtensionData() As _ System.Runtime.Serialization.ExtensionDataObject Implements _ System.Runtime.Serialization.IExtensibleDataObject.ExtensionData Get Return Me.extensionDataField End Get Set Me.extensionDataField = value End Set End Property <System.Runtime.Serialization.DataMemberAttribute()> _ Public Property FirstName() As String Get Return Me.FirstNameField End Get Set Me.FirstNameField = value End Set End Property <System.Runtime.Serialization.DataMemberAttribute()> _ Public Property LastName() As String Get Return Me.LastNameField End Get Set Me.LastNameField = value End Set End Property End Class
Using this model, you can easily build your services with your own defined types.