Share via


Use WSDAPI for .NET

3/21/2011

The Microsoft Web Services on Devices API for .NET (WSDAPI for .NET) Framework supports the implementation of hosted services and service clients conforming to the Devices Profile for Web Services (DPWS) specification. DPWS is a Web Services profile that enables plug-and-play for networked devices. A computer can detect DPWS-enabled devices on a network, then discover and invoke the Web service functionality each device provides. Its purpose is similar to Universal Plug and Play (UPnP), although it employs a Web Services model.

The WSDAPI for .NET Framework provides the following key features:

  • Web Services WS device and function discovery.
  • Support for Message Transmission Optimization Mechanism (MTOM) attachments.
  • Compatible with Plug and Play Extensions (PnP-X) function discovery.
  • Compatible with all other Microsoft DPWS implementations, such as WSDAPI.
  • Code generator for minimizing the amount of "glue" code the developer must write.

Important

Download the files for the sample applications from this Microsoft Web site.

To learn more about the capabilities of the WSDAPI for .NET Framework, see the API documentation and sample code on MSDN.

WSDAPI for .NET Application Development Tutorial

WSDAPI for .NET supports the "contract first" approach where the communication is defined in a Web Service Definition Language (WSDL) file. This contract is processed by the DpwsCodeGen tool, which creates the wrapper code. The DPWSCodeGen command-line application is located in the EmbeddedSDK\bin subfolder in the Windows Embedded Standard 7 installation folder, as shown in the following illustration.

Ff793722.9b4e939f-cd95-44df-b12d-c177bcfc5aaf(en-US,WinEmbedded.1001).gif

In this tutorial you will create a WSDL contract, generate the wrapper code, and add code for the client and service functionality. The system will model a stock-quote service on a device that can be queried by a client. The client communicates a stock-ticker symbol to the service on the device, and the device passes back a decimal value representing the current price.

You can start the development process by using the following sample WSDL file:

<?xml version="1.0"?>
<wsdl:definitions name="StockQuoteService"

  targetNamespace="https://schemas.microsoft.com/stockquote/definitions"
  xmlns:tns="https://schemas.microsoft.com/stockquote/definitions"
  xmlns:soap="https://schemas.xmlsoap.org/wsdl/soap/"
  xmlns:wsdl="https://schemas.xmlsoap.org/wsdl/"
  xmlns:xs="http://www.w3.org/2001/XMLSchema" >

  <wsdl:types>
    <xs:schema 
      targetNamespace="https://schemas.microsoft.com/stockquote/definitions"
      xmlns:tns="https://schemas.microsoft.com/stockquote/definitions"
      xmlns:xs="http://www.w3.org/2001/XMLSchema"
      elementFormDefault="qualified"
      blockDefault="#all" >

      <xs:element name="TradePriceRequest">
        <xs:complexType>
          <xs:all>
            <xs:element name="tickerSymbol" type="xs:string"/>
          </xs:all>
        </xs:complexType>
      </xs:element>

      <xs:element name="TradePriceResponse">
        <xs:complexType>
          <xs:all>
            <xs:element name="price" type="xs:float"/>
          </xs:all>
        </xs:complexType>
      </xs:element>
    </xs:schema>
  </wsdl:types>

  <!-- ************************************************************ -->
  <!-- The following messages section describes how the types are
       assembled into messages. These messages are referenced in
       the operations listed in the portType section below.
  -->
  <!-- ************************************************************ -->
  <wsdl:message name="GetLastTradePriceInput">
    <wsdl:part name="parameters" element="tns:TradePriceRequest" />
  </wsdl:message>

  <wsdl:message name="GetLastTradePriceOutput">
    <wsdl:part name="parameters" element="tns:TradePriceResponse" />
  </wsdl:message>

  <!-- ************************************************************ -->
  <!-- The portType section organizes messages into operations.     -->
  <!--  ************************************************************ -->
  <wsdl:portType name="StockQuotePortType">
    <wsdl:operation name="GetLastTradePrice">
      <wsdl:input message="tns:GetLastTradePriceInput"/>
      <wsdl:output message="tns:GetLastTradePriceOutput"/>
    </wsdl:operation>
  </wsdl:portType>
</wsdl:definitions>

The WSDL file in the previous code sample is separated into definitions of elements and data types that are used (TradePriceRequest and TradePriceResponse), the definition of the messages that are sent (GetLastTradePriceInput and GetLastTradePriceOutput), and the order and connections between the messages (GetLastTradePrice).

To generate code for this WSDL file you must execute DpwsCodeGen.exe with the WSDL file name as a parameter; for example:

DpwsCodeGen.exe stockquote.wsdl

This will generate the following text:

Creating DataContract:
        Class Name: TradePriceRequest
        Class Namespace: https://schemas.microsoft.com/stockquote/definitions
Creating DpwsDataContractSerializer:
        Class Name: TradePriceRequestDataContractSerializer

Creating DataContract:
        Class Name: TradePriceResponse
        Class Namespace: https://schemas.microsoft.com/stockquote/definitions
Creating DpwsDataContractSerializer:
        Class Name: TradePriceResponseDataContractSerializer

Creating hosted service operation:
        Operation Name: GetLastTradePrice
Creating client proxy method:
        Operation Name: GetLastTradePrice

When the code generator finishes running there are three new files in the directory, as follows:

  • StockquoteContract.cs
    This file contains the type definitions for the transferred data (classes for TradePriceRequest and TradePriceResponse), the serializers for these types, and the interface for this service defining the methods that must be implemented.
  • StockquoteHostedService.cs
    This file contains the generated wrapper code for the hosted service on a device. This code performs the deserialization of the transferred data.
  • StockquoteProxy.cs
    This file contains the generated wrapper code for the service client. It offers all the methods from the service and takes care of the transmission and execution of the remote function calls.

To implement the device logic you must implement the IStockQuote interface with problem domain-specific code. In this case, it is returning the price for a given ticker symbol, as follows:

internal class StockQuoteServiceImplementation : IStockQuoteService
{
  public TradePriceResponse GetLastTradePrice(TradePriceRequest request)
  {
    return new TradePriceResponse { price = 23.42f };
  }
}

To set up the device, you must add the following code:

class StockQuoteDevice
{
  public static void Main(string[] args)
  {
    using (DpwsDevice dpwsDevice = new DpwsDevice())
    {
      dpwsDevice.AcceptLocalRequest = true;

      // Set device information
      dpwsDevice.ThisModel.Manufacturer.Add(CultureInfo.CurrentCulture, 
                                            "Microsoft Corporation");
      dpwsDevice.ThisModel.ManufacturerUrl = new Uri("https://www.microsoft.com/");
      dpwsDevice.ThisModel.ModelName.Add(CultureInfo.CurrentCulture, 
                                         "StockQuote Device");
      dpwsDevice.ThisModel.ModelNumber = "1.0";
      dpwsDevice.ThisModel.ModelUrl = new Uri("https://www.microsoft.com/");
      dpwsDevice.ThisModel.PresentationUrl = new Uri("https://www.microsoft.com/");
      dpwsDevice.ThisDevice.FriendlyName.Add(CultureInfo.CurrentCulture,
                                             "StockQuoteService");
      dpwsDevice.ThisDevice.FirmwareVersion = "alpha";
      dpwsDevice.ThisDevice.SerialNumber = "12345678";

      dpwsDevice.MetadataVersion = 1;

      // Add the hosted service to the device
      dpwsDevice.AddHostedService(
          new StockQuoteService(new StockQuoteServiceImplementation()));

      // Set logical address for the host
      dpwsDevice.Host.EndpointReferences.Add(
          new DpwsEndpointReference(
              "urn:uuid:a2d75d2f-11DA-47ac-8aca-78a8010d0c1f"));

      dpwsDevice.Host.Types.Add( new XmlQualifiedName("StockQuoteDeviceType",
                                              "https://schemas.microsoft.com"));

      dpwsDevice.IPAddressType = DpwsIPAddressType.Auto;
      dpwsDevice.Start();
      Thread.Sleep(-1);
    }
  }
}

After creating a new device instance, the properties for ThisDevice and ThisModel can be set. These values will be sent when the device is queried by other clients (for more information, see Section 5.1 of the Devices Profile for Web Sevices specification). Setting these values is optional but is highly recommended because they describe the device and could possibly be presented to a user, as shown in the following illustration.

Ff793722.a73aa548-ce63-4ef3-aea4-4161939265cf(en-US,WinEmbedded.1001).gif

One required step while configuring the device is setting the endpoint reference of the host because this is the address that will be returned when this device is queried.

Adding a service type makes it possible to probe for this device later.

Calling the Start() method of the device is needed to begin listening to network traffic, such as discovery messages or client requests.

The resulting source code for the device class, including the endpoint reference and Start method, might look like the following:

namespace device
{
  using System;
  using System.Globalization;
  using System.Threading;
  using System.Xml;
  using Microsoft.Web.Services.Dpws;
  using schemas.microsoft.com.stockquote.definitions;

  class StockQuoteDevice
  {
    public static void Main(string[] args)
    {
      using (DpwsDevice dpwsDevice = new DpwsDevice())
      {
        dpwsDevice.AcceptLocalRequest = true;

        // Set ThisModel & ThisDevice properties
        
        // Add the hosted service to the device
        dpwsDevice.AddHostedService(new StockQuoteService(
                                        new StockQuoteServiceImplementation()));

        // Set logical address for the host
        dpwsDevice.Host.EndpointReferences.Add(
            new DpwsEndpointReference(
                "urn:uuid:a2d75d2f-11DA-47ac-8aca-78a8010d0c1f"));

 // Set type for the host 
        dpwsDevice.Host.Types.Add(
            new XmlQualifiedName(
                "StockQuoteDeviceType", "https://schemas.microsoft.com"));
        dpwsDevice.Start();
        Thread.Sleep(-1);
      }
  }
}

  internal class StockQuoteServiceImplementation : IStockQuoteService
  {
    // Implement service logic.
  }
}

After starting the device, an icon representing the device appears in the network view of Explorer, as shown in the following illustration.

Ff793722.68af328b-f243-49d6-a685-8a375bba55be(en-US,WinEmbedded.1001).gif

The device icon is clickable and points to the URL that was set as the dpwsDevice.ThisModel.PresentationUrl property.

That is all that must be done to create a device exposing the stock-quote service to the network. Implementing the client is simple as well, but requires more code to discover devices on the network.

All communication from the client to the service is done by calling methods from the generated code of the client proxy. The proxy handles the serialization and transmission of the data. This means that a new instance of the client proxy must be initialized first in the client class.

public Client()
{
  StockQuoteServiceProxy stockQuoteServiceProxy = new StockQuoteServiceProxy()
  {
    // Turn on listening to this IP
    AcceptLocalRequest = true;
  }

The next step is to make the client listen to the network traffic, to catch all discovery messages, as follows:

// Start the clients to make them listen to http and udp traffic
  stockQuoteServiceProxy.Start();

You can take two different approaches to discover devices and their services, as follows:

  1. The client can listen to the network for "hello" and "bye" messages of devices to know about the presence and query the devices for their services.
  2. The client can send a probe request for a service and process the responses.

To catch the discovery events, you can add listeners to stockQuoteServiceProxy.ClientDiscovery.HelloEvent or stockQuoteServiceProxy.ClientDiscovery.ByeEvent.

These listeners will be called with DpwsServiceMetadataEventArgs parameters containing the device host’s endpoint reference and transport address. An example listener might look like the following:

public void HelloEventHandler( object sender, 
                               DpwsServiceMetadataEventArgs helloEventArgs )
{
  Console.WriteLine(
                   "Client received a hello request from Endpoint address "
                   + helloEventArgs.EndpointReference.Address);
}

Probing means searching for a service that supports a specific type, which is an XmlQualifiedName. First, a structure containing the types a client is looking for must be filled, as follows:

DpwsServiceTypeCollection searchTypes = new DpwsServiceTypeCollection();
searchTypes.Add( new XmlQualifiedName( "StockQuoteDeviceType",
                                       "https://schemas.microsoft.com"));

This structure can then be passed to the discovery client of the service proxy, as follows:

DpwsServiceDescriptionCollection probeMatches =
        stockQuoteServiceProxy.ClientDiscovery.Probe(searchTypes, null, 0);

After a timeout, this call returns a list of matches. If the matches should be received asynchronously, add a listener method to

stockQuoteServiceProxy.ClientDiscovery.ProbeMatchEvent.

These two methods return either a single instance or a list of DpwsServiceMetadataEventArgs that contain all information necessary to connect to a service on a device. The EventArgs class contains the collection of TransportAddresses, which can be used to contact the device host. However, transferring content in the XAddr field of the SOAP header is optional, and this collection might be empty. In this case, a second step called "Resolving" is needed. This step is basically another broadcast requesting a transport address for a given endpoint reference. This can be done as follows:

DpwsServiceMetadataEventArgs resolveMatch = 
    stockQuoteServiceProxy.ClientDiscovery.Resolve(probeMatch.EndpointReference);

The first parameter of this call is one of the transport addresses of the device host, and the second one is the endpoint reference of the device. Both can be found in the DpwsServiceMetadataEventArgs object.

If the request was successful, the returned DpwsMetadata instance is not null and contains, in addition to the ThisModel and ThisDevice metadata, a relationship part that describes the available hosted services. DpwsMetadata.Relationship.Host describes the device host whose data was already known partly from the probe/resolve responses. The most important property of the relationship metadata is the collection DpwsMetadata.Relationship.HostedServices. For each hosted service there is an endpoint reference list which should always be a list of http transport addresses (see R0042 of the Devices Profile for Web Sevices specification). This value can be used to directly communicate with the service.

Another important collection is DpwsMetadata.Relationship.HostedServices[].Types. To determine if one of the hosted services is usable, check whether the desired type is contained in this collection, as follows:

hostedService.Types.Contains( 
    new XmlQualifiedName("StockQuoteService",   
                         "https://schemas.microsoft.com/stockquote/definitions")))

If the type the client is searching for is included in the probe match response, a matching service has been identified. To communicate with the service, store its network address, as follows:

IEnumerator<DpwsEndpointReference> iterator =  
        hostedService.EndpointReferences.GetEnumerator();
iterator.MoveNext();
stockQuoteServiceProxy.ServiceEndpoint = iterator.Current;

Now you can use the service proxy to call the remote methods, as follows:

try
{
  TradePriceRequest tradePriceRequest = new TradePriceRequest
  {
    tickerSymbol = “XYZ”
  };

  TradePriceResponse tradePriceResponse = 
    stockQuoteServiceProxy.GetLastTradePrice( tradePriceRequest );

  Console.WriteLine (“XYZ: “ + tradePriceRequest.price + “$”);
}
catch (Exception e)
{
  Console.WriteLine(string.Empty);
  Console.WriteLine("StockQuote service failed. " + e.Message);
}

The resulting source code for the client, including the code in the previous section, might look like the following:

namespace client
{
  using System;
  using System.Collections.Generic;
  using System.Threading;
  using System.Xml;
  using Microsoft.Web.Services.Dpws;
  using schemas.microsoft.com.stockquote.definitions;

  class StockQuoteClient
  {
    public static void Main(string[] args)
    {
      StockQuoteServiceProxy stockQuoteServiceProxy = new StockQuoteServiceProxy()
      {
        // Turn on listening to this IP
        AcceptLocalRequest = true
      };

      stockQuoteServiceProxy.ClientDiscovery.HelloEvent += HelloEventHandler;
      stockQuoteServiceProxy.ClientDiscovery.ByeEvent += ByeEventHandler;
      stockQuoteServiceProxy.ClientDiscovery.ProbeMatchEvent += 
          ProbeMatchEventHandler;

      // Start the clients to make them listen to http and udp traffic
      stockQuoteServiceProxy.Start();
      Thread.Sleep(3000); // wait for the device to be ready

      // Probe for a specific type of device
      DpwsServiceTypeCollection searchTypes = new DpwsServiceTypeCollection();
      searchTypes.Add(new XmlQualifiedName("StockQuoteDeviceType",
                                           "https://schemas.microsoft.com"));
 
      DpwsServiceDescriptionCollection probeMatches = 
          stockQuoteServiceProxy.ClientDiscovery.Probe(searchTypes, null, 0);

      foreach (DpwsServiceMetadataEventArgs probeMatch in probeMatches)
      {
        ICollection<Uri> transportAddresses = probeMatch.TransportAddresses;

        if ( probeMatch.TransportAddresses == null || 
             probeMatch.TransportAddresses.Count == 0)
        {
          DpwsServiceMetadataEventArgs resolveMatch =
              stockQuoteServiceProxy.ClientDiscovery.Resolve(
                  probeMatch.EndpointReference);
          transportAddresses = resolveMatch.TransportAddresses;
        }

        IEnumerator<Uri> transportAddressIter = transportAddresses.GetEnumerator();
        transportAddressIter.MoveNext();
        Uri deviceTransportAddress = transportAddressIter.Current;

        DpwsMetadata deviceMetadata = DpwsClientMetadata.Get(
            deviceTransportAddress, probeMatch.EndpointReference);

        foreach (DpwsServiceMetadata hostedService in 
                 deviceMetadata.Relationship.HostedServices)
        {
          if ( hostedService.Types.Contains(
                   new XmlQualifiedName(
                        "StockQuoteService",           
                        "https://schemas.microsoft.com/stockquote/definitions")))
          {
            IEnumerator<DpwsEndpointReference> iterator = 
                hostedService.EndpointReferences.GetEnumerator();
            iterator.MoveNext();
            stockQuoteServiceProxy.ServiceEndpoint = iterator.Current;
          }
        }
      }

      if (stockQuoteServiceProxy.ServiceEndpoint == null)
      {
        Console.WriteLine("Probing the device failed");
      }
      else
      {
        // Now the service proxy has a ServiceEndpoint
        try
        {
          TradePriceRequest tradePriceRequest = new TradePriceRequest 
              { tickerSymbol = "MSFT" };

          TradePriceResponse tradePriceResponse = 
              stockQuoteServiceProxy.GetLastTradePriceRequest(tradePriceRequest);

          Console.WriteLine("MSFT: " + tradePriceResponse.price + "$");
        }
        catch (Exception e)
        {
          Console.WriteLine(string.Empty);
          Console.WriteLine("StockQuote service failed. " + e.Message);
        }
      }
    }

    public static void HelloEventHandler(object sender,  
                                         DpwsServiceMetadataEventArgs eventArgs)
    {
      Console.WriteLine(
          "Client received a hello request from Endpoint address " +   
          eventArgs.EndpointReference.Address);
    }

    public static void ByeEventHandler(object sender, 
                                       DpwsServiceMetadataEventArgs eventArgs)
    {
      Console.WriteLine(
          "Client received a bye request from Endpoint address " + 
          eventArgs.EndpointReference.Address);
    }

    public static void ProbeMatchEventHandler(object sender,
                                            DpwsServiceMetadataEventArgs eventArgs)
    {
      Console.WriteLine(
          "Client received a probe match from Endpoint address " + 
          eventArgs.EndpointReference.Address);
    }    
  }
}

You can compile the code by using the following command:

C:\Windows\Microsoft.NET\Framework\v3.5\csc.exe    \
    /out:stockquote_client.exe /target:exe         \
    ..\contract\generated\stockquoteContract.cs    \
    ..\contract\generated\stockquoteProxy.cs       \
    StockQuoteClient.cs                            \
    /reference: Microsoft.Web.Services.Dpws.dll

Note

You can find the referenced library in the Value Add folder on the Windows Embedded Standard 7 installation media. It is also possible to reference the Microsoft.Web.Services.Dpws.metadata.dll from the EmbeddedSDK folder in the Standard 7 installation path instead of the Microsoft.Web.Services.Dpws.dll. The metadata dll is a dehydrated dll publishing the API of the actual library. The project will compile, but the built assemblies will only run on a device that contains the WSDAPI for .NET component.

To test the functionality, you must start the device first and then start the client, as follows:

(cmd) start device\stockquote_device.exe && start client\stockquote_client.exe

(PowerShell) Start-Process .\device\stockquote_device.exe; Start-Process \ .\client\stockquote_client.exe

The output of the client will look similar to the following:

Client received a hello request from Endpoint address 
        urn:uuid:a2d75d2f-11DA-47ac-8aca-78a8010d0c1f
Client received a probe match from Endpoint address 
        urn:uuid:a2d75d2f-11DA-47ac-8aca-78a8010d0c1f
Client received a hello request from Endpoint address 
        uuid:3425fa85-c8c7-4903-a13a-744b0a104c8d
XYZ: 23,42$

If the executable is run in Visual Studio, a lot of informational output appears in the Output window (View -> Output), as shown in the following output sample (having this information can speed up the debugging process):

IPv4 UdpListener binds to: 0.0.0.0:3702
IPv6 UdpListener binds to: :::3702

Udp service host started...

Http service host started...
IPv4 UdpListener binds to: 0.0.0.0:3702

Listening on the following Http prefixes:
IPv6 UdpListener binds to: :::3702

Udp service host started...

Http service host started...

Listening on the following Http prefixes:
IPv4 UdpListener binds to: 0.0.0.0:3702
IPv6 UdpListener binds to: :::3702

Udp service host started...

Http service host started...

Listening on the following Http prefixes:
   http://*:5357/89de9917-de97-4175-9e95-26ec6b796f76/
   http://*:5357/62fd470d-84b9-469b-850c-7d871d8194e0/
   http://*:5357/3cb0d1ba-cc3a-46ce-b416-212ac2419b99/

Advanced Programming with WSDAPI for .NET

Transferring binary data by using attachments

The previous section described basic message exchange. You can use this communication methodology for one-way or two-way communication of complex data with the service. The data types are defined in the WSDL and are represented by generated classes. These data types can be complex and might contain a variety of data types.

All this data will be serialized to XML, which is appropriate for most data, but is inefficient for large BLOBs of data, such as images. To transfer that type of data, attachments are used where the binary data fields are optimally encoded using MTOM. If the data type xs:base64Binary is used in the WSDL file, the generated code automatically uses MTOM transports, and the data is serialized accordingly. The generated classes for the data-type map base64Binaries to byte[].

The following is an example of an operation that uses binary data:

<wsdl:definitions 
    targetNamespace="https://schemas.microsoft.com/AttachmentService" 
    xmlns:tns="https://schemas.microsoft.com/AttachmentService"
    xmlns:wsa="https://schemas.xmlsoap.org/ws/2004/08/addressing" 
    xmlns:wsdl="https://schemas.xmlsoap.org/wsdl/" 
    xmlns:wsdp="https://schemas.xmlsoap.org/ws/2005/05/devprof" 
    xmlns:wsp="https://schemas.xmlsoap.org/ws/2004/09/policy" 
    xmlns:wsoap12="https://schemas.xmlsoap.org/wsdl/soap12/"
    xmlns:wsu=
"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility1.0.xsd"
    xmlns:wsx="https://schemas.xmlsoap.org/ws/2004/09/mex"
    xmlns:wsf="https://schemas.xmlsoap.org/ws/2004/09/transfer">
    <wsp:Policy wsu:Id="Attachment">
        <wsdp:Profile />
    </wsp:Policy>
    <wsdl:types>
        <xs:schema 
          targetNamespace="https://schemas.microsoft.com/AttachmentService"
          xmlns:tns="https://schemas.microsoft.com/AttachmentService" 
          xmlns:xs="http://www.w3.org/2001/XMLSchema"
          elementFormDefault="qualified" 
          blockDefault="#all" >
            <xs:element name="OneWayAttachment" type="tns:OneWayAttachmentType" />
          
            <xs:complexType name="OneWayAttachmentType" >
                <xs:sequence>
                    <xs:element name="Param" type="xs:base64Binary" />
                </xs:sequence>
            </xs:complexType>
        </xs:schema>
    </wsdl:types>
    <wsdl:message name="OneWayAttachmentMessageIn">
        <wsdl:part name="parameters" element="tns:OneWayAttachment" />
    </wsdl:message>
   
 <wsdl:portType name="AttachmentService">
        <wsdl:operation name="OneWayAttachment">
            <wsdl:input
                message="tns:OneWayAttachmentMessageIn"
                wsa:Action=
                "https://schemas.microsoft.com/AttachmentService/OneWayAttachment"/>
        </wsdl:operation>
</wsdl:definitions>

The generated interface for the hosted service, for the WSDL in the previous section, will look like the following:

public interface IAttachmentService
{
  void OneWayAttachment(OneWayAttachmentType request);
}
The generated definition of the OneWayAttachmentType class will look like this:
public class OneWayAttachmentType
{
  [DataMember(Order=0, DataType="base64Binary", IsRequired=true)]
  public byte[] Param;
        
  [DataMember(IsNillable=true, IsRequired=true, MinOccurs=2, MaxOccurs=2)]
  public XmlElement[] AnyElement;
        
  [DataMember(IsNillable=true, IsRequired=false)]
  public XmlAttribute[] AnyAttribute;
}

To send data from the client to the service, you must call the method from the service proxy, as follows:

public virtual void OneWayAttachment(OneWayAttachmentType request)

Subscribing to and receiving events

Usually, all messages are initiated by the client (one-way); the service might return data (two-way). Events are messages from the service to the client that can occur at any time.

Defining an event in the WSDL is similar to one- or two-way messages, but an event only defines an output message in the operation, as follows:

<!-- Place this into the xs:schema definition -->
<xs:element name="IntegerEvent" type="xs:integer" />

<!-- Place this into the wsdl:type definition -->
<wsdl:message name="IntegerEventMessageOut">
  <wsdl:part name="parameters" element="tns:IntegerEvent" />
</wsdl:message>

<!-- Place this into the wsdl:portType definition -->
<wsdl:operation name="IntegerEvent">
  <wsdl:output
    message="tns:IntegerEventMessageOut"
    wsa:Action="https://schemas.microsoft.com/IntegerEvent"/>
</wsdl:operation>

A complete WSDL file describing a service with a single event might look like the following:

<wsdl:definitions 
  targetNamespace="https://schemas.microsoft.com/EventingService" 
  xmlns:tns="https://schemas.microsoft.com/EventingService"
  xmlns:wsa="https://schemas.xmlsoap.org/ws/2004/08/addressing" 
  xmlns:wsdl="https://schemas.xmlsoap.org/wsdl/"
  xmlns:wsdp="https://schemas.xmlsoap.org/ws/2005/05/devprof" 
  xmlns:wse="https://schemas.xmlsoap.org/ws/2004/08/eventing" 
  xmlns:wsp="https://schemas.xmlsoap.org/ws/2004/09/policy" 
  xmlns:wsoap12="https://schemas.xmlsoap.org/wsdl/soap12/"
  xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
  xmlns:wsx="https://schemas.xmlsoap.org/ws/2004/09/mex"
  xmlns:wsf="https://schemas.xmlsoap.org/ws/2004/09/transfer" >

  <wsp:Policy wsu:Id="Eventing" >
    <wsdp:Profile />
    <wsdp:PushDelivery />
    <wsdp:DurationExpiration />
    <wsdp:ActionFilter />
  </wsp:Policy>

  <wsdl:types>
    <xs:schema 
      targetNamespace="https://schemas.microsoft.com/EventingService"
      xmlns:tns="https://schemas.microsoft.com/EventingService" 
      xmlns:xs="http://www.w3.org/2001/XMLSchema"
      elementFormDefault="qualified" 
      blockDefault="#all">

      <xs:element name="IntegerEvent" type="tns:IntegerEventType" />
      <xs:complexType name="IntegerEventType">
        <xs:sequence>
          <xs:element name="Param" type="xs:int" />
        </xs:sequence>
      </xs:complexType>
    </xs:schema>
  </wsdl:types>

  <wsdl:message name="IntegerEventMessageOut">
    <wsdl:part name="parameters" element="tns:IntegerEvent" />
  </wsdl:message>

  <wsdl:portType name="EventingService" wse:EventSource="true" >
    <wsdl:operation name="IntegerEvent">
      <wsdl:output
        message="tns:IntegerEventMessageOut"
        wsa:Action="https://schemas.microsoft.com/EventingService/IntegerEvent"/>
      </wsdl:operation>
  </wsdl:portType>
</wsdl:definitions>

Using DpwsCodeGen again on the new WSDL file creates all wrapper methods for the client and service.

All that is required to create an event on the service is an instance of the generated class for the hosted service, as follows:

EventingService eventingService = new EventingService();
dpwsDevice.AddHostedService(eventingService);
dpwsDevice.Start();

...

try
{
  EventingService.IntegerEvent(42);
  Console.WriteLine(" * -> Fired IntegerEvent with value={0}", 42);
}
catch (Exception e)
{
  Console.WriteLine("IntegerEvent FireEvent failed: " + e.Message);
}

As shown above, the instance of this hosted service must be registered in the device, with DpwsDevice.AddHostedService, before it can be used.

The service sends events to all subscribed clients. To subscribe to one or more events, a client must initialize a DpwsSubscriptionRequest instance and send a subscription request by calling the DpwsClientEventing.Subscribe method, as follows:

IEnumerator<DpwsEndpointReference> endpointIterator =
    eventingServiceClient.EndpointReferences.GetEnumerator();
endpointIterator.MoveNext(); 

// Test service host call
DpwsSubscriptionRequest subscriptionRequest =
    new DpwsSubscriptionRequest(
      new Collection<Uri>
      {
        new Uri("https://schemas.microsoft.com/EventingService/SimpleEvent")
      },
      eventingServiceClient.ServiceEndpoint, // transport address of svc
      endpointIterator.Current, // transport address of the client proxy
      new DpwsDuration("PT65S"),
      null);

The result of a subscription request is a DpwsSubscriptionResponse instance that is not null. This DpwsSubscriptionResponse instance contains the new duration of the subscription.

Note

The service might change the requested duration to a different value (for more information, see section 3.1 in the Web Services Eventing specification).

The second piece to implement on the client, in addition to the subscription, is the callback method that will be called if the event arrives on the client.

First, a new class implementing a generated interface is needed (the interface is located in the generated file stockquoteContract.cs), as follows:

internal class EventingClientImplementation : IEventingServiceCallback
{
  public void IntegerEvent(IntegerEventType request)
  {
    Console.WriteLine(string.Empty);
    Console.WriteLine(" * -> IntegerEvent received. Value={0}", request.Param);
  }
}

An instance of this class is passed to the constructor of the generated proxy class for initialization, as follows:

eventingServiceClient =
  new EventingServiceProxy( new EventingClientImplementation() )
  {
    AcceptLocalRequest = true
  };
eventingServiceClient.Start();

Once the connection is established, the client can start subscribing to events, and receive them.

The complete source code for the eventing client looks like the following:

namespace client
{
  using System;
  using System.Collections.Generic;
  using System.Collections.ObjectModel;
  using System.Threading;
  using System.Xml;
  using Microsoft.Web.Services.Dpws;
  using schemas.microsoft.com.eventing;

  class EventingClient
  {
    public static void Main(string[] args)
    {
      EventingServiceProxy eventingServiceProxy = 
        new EventingServiceProxy(new EventingClientImplementation())
      {
        // Turn on listening to this IP
        AcceptLocalRequest = true
      };

      // Start the clients to make them listen to http and udp traffic
      eventingServiceProxy.Start();
      Thread.Sleep(3000);

      // Probe for a specifiv type of device device
      DpwsServiceTypeCollection searchTypes = new DpwsServiceTypeCollection();
      searchTypes.Add(new XmlQualifiedName(
          "EventingDeviceType", "https://schemas.microsoft.com"));

      DpwsServiceDescriptionCollection probeMatches =   
          eventingServiceProxy.ClientDiscovery.Probe(searchTypes, null, 0);

      foreach (DpwsServiceMetadataEventArgs probeMatch in probeMatches)
      {
        ICollection<Uri> transportAddresses = probeMatch.TransportAddresses;

        if (probeMatch.TransportAddresses == null || 
            probeMatch.TransportAddresses.Count == 0)
        {
          DpwsServiceMetadataEventArgs resolveMatch =
              eventingServiceProxy.ClientDiscovery.Resolve(
                probeMatch.EndpointReference);
          transportAddresses = resolveMatch.TransportAddresses;
        }

        IEnumerator<Uri> transportAddressIter = 
            transportAddresses.GetEnumerator();
        transportAddressIter.MoveNext();
        Uri deviceTransportAddress = transportAddressIter.Current;

        DpwsMetadata deviceMetadata = DpwsClientMetadata.Get(
            deviceTransportAddress, probeMatch.EndpointReference);

        foreach (DpwsServiceMetadata hostedService in 
            deviceMetadata.Relationship.HostedServices)
        {
          if ( hostedService.Types.Contains(
              new XmlQualifiedName("EventingService", 
                  "https://schemas.microsoft.com/eventing")))
          {
            IEnumerator<DpwsEndpointReference> iterator = 
                hostedService.EndpointReferences.GetEnumerator();
            iterator.MoveNext();
            eventingServiceProxy.ServiceEndpoint = iterator.Current;
          }
        }
      }

      if (eventingServiceProxy.ServiceEndpoint == null)
      {
        Console.WriteLine("Probing the device failed");
      }
      else
      {
        // Now the service proxy has a ServiceEndpoint
        try
        {
          // Get the first address of the client
          // This will be the address the service sends events to
          IEnumerator<DpwsEndpointReference> endpointIterator = 
              eventingServiceProxy.EndpointReferences.GetEnumerator();
          endpointIterator.MoveNext();

          DpwsSubscriptionRequest subscriptionRequest =
              new DpwsSubscriptionRequest(
                new Collection<Uri> { 
                new Uri("https://schemas.microsoft.com/eventing/IntegerEvent") },
                eventingServiceProxy.ServiceEndpoint, // transport address of svc
                endpointIterator.Current, // transport address client proxy
                new DpwsDuration("PT65S"),
                null);

          DpwsEventSubscription eventSubscription = 
              DpwsClientEventing.Subscribe(
                  new Uri(subscriptionRequest.EndpointReference.Address),
                  subscriptionRequest);

          Console.WriteLine("Subscribed for a duration of " +
                            eventSubscription.Expires.DurationInSeconds + 
                            " seconds.");
        }
        catch (Exception e)
        {
          Console.WriteLine(string.Empty);
          Console.WriteLine("StockQuote service failed. " + e.Message);
        }
      }
    }
  }

  internal class EventingClientImplementation : IEventingServiceCallback
  {
    public void IntegerEvent(IntegerEventType request)
    {
      Console.WriteLine(string.Empty);
      Console.WriteLine(" * -> IntegerEvent received. Value={0}", request.Param);
    }
  }
}

Extensible WSDL files and services

Any attributes and elements are extension constructs. You can use these attributes and elements to design a service in a "future proof" way. They enable you to transfer additional elements, without specifying them, when writing the contract.

Reusing the OneWayAttachmentType code, an extensible type might look like the following:

<xs:complexType name="OneWayAttachmentType" >
                <xs:sequence>
                    <xs:element name="Param" type="xs:base64Binary" />
                    <xs:any minOccurs="0" maxOccurs="unbounded"
                        namespace="##other" processContents="lax" />
                </xs:sequence>
                <xs:anyAttribute namespace="##other" 
                    processContents="lax" />
            </xs:complexType>

With this definition the generated data type gets additional properties, as follows:

  [DataMember(IsNillable=true, IsRequired=true, MinOccurs=2, MaxOccurs=2)]
  public XmlElement[] AnyElement;
        
  [DataMember(IsNillable=true, IsRequired=false)]
  public XmlAttribute[] AnyAttribute;

Using DpwsCodeGen.exe

The following are command-line switches that you can use with DpwsCodeGen.exe:

/F

/Force

The default behavior is to not overwrite generated files. In rare cases it might be necessary to change the generated files manually. To prevent these changes from being lost, you must specify this option to overwrite the files. You should use this switch with caution.

/V

/Verbose

Shows more verbose output while processing the WSDL files.

/C

/ContractFilename

Specifies the contract file name. As a default, the WSDL file name will be used to determine the file names of the generated source files. Using this flag changes that behavior. It is also possible to specify relative or absolute paths with this switch; for example, /C:..\..\myContract_.

/S

/ServiceId

Each time DpwsCodeGen is called for a WSDL file, the service ID of the service is generated in a random way. Using this switch allows developers to specify the service ID and make it the same for each subsequent call.

Ff793722.note(en-US,WinEmbedded.1001).gifNote:
This switch also allows "non urn:uuid:" style service IDs; for example, /S:urn:myservice.

/N

/CodeNamespace

Typically, the namespace in the generated source is the same as the one given in the WSDL file. If the namespace is different, this switch can be used.

The DpwsCodeGen.exe tool can also be used to create code for existing services. In this case, the WSDL file must be saved manually to a local file, after which it can be executed as explained above.