Programming Web Services


Web services are methods that can be called by any client on any platform that communicates through public network and XML protocols. In this section, I ll show you how to define the Web service by using the Indigo framework. I ll also show you how to generate information that describes the service in a platform- agnostic manner. You typically publish this information so that clients on any platform can use your Web service. Finally, I ll show you how to call the service by using a Indigo client application.

Defining a Web Service

First, we need a Web service that clients can call. You perform the following steps to create a Web service using the Indigo Framework:

  1. Define a class with one or more public methods.

  2. Apply the DatagramPortTypeAttribute to the class.

  3. Apply the ServiceMethodAttribute to the public methods you want to expose as Web service methods. Note that you can have other methods in the class that aren t exposed to Web service clients.

  4. Compile the class into a library assembly.

  5. Host the service in a host application.

In the following example, we create the TimeService assembly, which contains a single class named WiseOwl.Time . The class contains a single Web service method named GetUtcTimeAndDate , which returns a string representation of the current Universal Time Coordinate (UTC) time for the specified culture.

 // TimeService.cs 
using System;
using System.Globalization;
using System.MessageBus;
using System.MessageBus.Services;

namespace WiseOwl {
[DatagramPortType(Name="Time",
Namespace="http://www.wiseowl.com/WebServices")]
public class Time {
[ServiceMethod]
public string GetUtcTimeAndDate(string culture) {
Console.WriteLine ("Client requested UTC time for culture {0}",
culture);
CultureInfo ci = new CultureInfo (culture);
return DateTime.UtcNow.ToString ("F", ci);
}
}
}

Hosting the Web Service

Generally, you will build Web service implementations into one or more library assemblies. You then need a host application that configures the Indigo Framework to listen for requests for that service. When Indigo receives a request for a service, it loads the Web service library, creates an instance of your service class, and calls the appropriate method on the instance.

You need to perform the following two steps to host an Indigo Web service:

  1. Call ServiceEnvironment.Load to acquire a service environment from a configuration file.

  2. Call ServiceEnvironment.Open to instruct MessageBus to begin listening for messages to this service.

If you are not using automatic activation, you must keep the application running if you want the service to continue processing messages. When your host application is a Windows Forms application or a Windows Service, the application automatically continues to run until it is explicitly shut down. Therefore, these types of applications typically need to do nothing special to keep the application running. However, as we re using a simple Console application as our host, we call Console.ReadLine to block the main thread until the user presses the Enter key to terminate the host application. Thread pool threads will service the Web service requests.

The following example is a generic host application. It hosts all Web services defined in the application s configuration file.

 // host.cs 
using System;
using System.MessageBus;
using System.MessageBus.Services;

class Host {
static void Main(string[] args) {
// The service environment needs to be loaded before it can be used.
// The Load method loads the configuration from the configuration file.
ServiceEnvironment se = null;
try {
se = ServiceEnvironment.Load ();

// Open the environment to allow client connections
se.Open ();
Console.WriteLine("Press enter to stop the services...");
Console.ReadLine ();
}
finally {
// Must close the environment to cleanup server promptly
if (se != null) se.Close ();
}
}
}

The generic host application instructs Indigo to obtain all its configuration information from the application s configuration file. As the host application is named host.exe, the application configuration file should be named host.exe.config and reside in the same directory as host.exe.

There are three main items of interest in this configuration file:

  • The main service environment definition

  • The port identityRole element

  • The activatableServices child elements

You can define multiple service environments in a configuration file; however, the ServiceEnvironment.Load method loads the environment named main by default so that s the only one we ve defined. We define a port mapped to the URL listed as the value of the identityRole element. Clients will use this URL to contact the server. The server will listen for requests addressed to this endpoint. The activatableServices element contains a list of the fully qualified types that the Indigo Framework can load and activate when receiving a request for the time. Recall that a fully qualified type name is a string consisting of the namespace-qualified type name, followed by a comma, followed by the full name of the assembly.

We ve also included a number of configuration entries to disable security for this simple application. Later in the chapter, I ll discuss communications security in more detail.

 <configuration> 
<system.messagebus>
<serviceEnvironments>
<serviceEnvironment name="main">
<port>
<identityRole>soap.tcp://localhost:46000/TimeService/</identityRole>
</port>
<!-- CAUTION: Security disabled for demonstration purposes only. -->
<remove name="securityManager" />
<policyManager>
<!-- CAUTION: Security disabled for demonstration purposes only. -->
<!-- Permits unsigned policy statements. Default requires signed policy statements -->
<areUntrustedPolicyAttachmentsAccepted>
true
</areUntrustedPolicyAttachmentsAccepted>
<isPolicyReturned>true</isPolicyReturned>
</policyManager>
<serviceManager>
<activatableServices>
<add type="WiseOwl.Time, TimeService" />
</activatableServices>
</serviceManager>
</serviceEnvironment>
</serviceEnvironments>
</system.messagebus>
</configuration>

Obtaining the WSDL for Your Web Service

Now that you have a working Web service, you d like to create a client that uses the Web service. Before you can write a client that uses the service, you ll need a description of the service. The Web Service Description Language (WSDL) 1.1 and extensions describe a contract that a Web service will uphold. Basically, WSDL allows Web service developers to publish a description of their services in a platform-agnostic manner. Typically, the first step in consuming a Web service is obtaining the WSDL description of the server. You can use the Wsdlgen.exe utility to produce the WSDL for your Indigo Web services.

To obtain WSDL for a Web Service, run the following command where < assemblyFileName > is the assembly containing the service: wsdlgen < assemblyFileName >.

In the prior example, I used " wsdlgen TimeService.dll ", and it produced two files: a .wsdl file and an .xsd file. The .wsdl file contains the description of the service: the name of the messages accepted by the server, the names of the parts of the messages (for example, the parameters), the port name and type used by the server, and the operations supported on the port.

 <!-- www_wiseowl_com.WebServices.wsdl --> 

<definitions xmlns:s="http://www.w3.org/2001/XMLSchema"
xmlns:gxa="http://schemas.xmlsoap.org/wsdl/gxa/2003/01/extensions"
xmlns:i0="http://www.wiseowl.com/WebServices"
xmlns:tns=http://www.wiseowl.com/WebServices
targetNamespace="http://www.wiseowl.com/WebServices"
xmlns="http://schemas.xmlsoap.org/wsdl/">
<import namespace="http://www.wiseowl.com/WebServices" />
<types />
<message name="GetUtcTimeAndDateRequest">
<part element="tns:GetUtcTimeAndDateRequest" name="parameters" />
</message>
<message name="GetUtcTimeAndDateResponse">
<part element="tns:GetUtcTimeAndDateResponse" name="parameters" />
</message>
<portType gxa:correlation="response" name="Time" gxa:usingName="TimeClient">
<operation name="GetUtcTimeAndDate" gxa:parameterOrder="tns:culture"
gxa:transaction="reject">
<input message="tns:GetUtcTimeAndDateRequest"
name="GetUtcTimeAndDateRequest" />
<output message="tns:GetUtcTimeAndDateResponse"
name="GetUtcTimeAndDateResponse" />
</operation>
</portType>
</definitions>

The .xsd file contains the XML schema definition of the messages mentioned in the .wsdl file. Note that the prior .wsdl file described an input message of type GetUtcTimeAndDateRequest and an output message of type GetUtcTimeAndDateResponse , both in the http://www.wiseowl.com/WebServices namespace. The following .xsd file contains the definition of those two XML types.

 <!-- www_wiseowl_com.WebServices.xsd --> 

<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:tns=http://www.wiseowl.com/WebServices
elementFormDefault="qualified"
targetNamespace="http://www.wiseowl.com/WebServices"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="GetUtcTimeAndDateRequest">
<xs:complexType>
<xs:sequence>
<xs:element minOccurs="0" name="culture" type="xs:string" />
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="GetUtcTimeAndDateResponse">
<xs:complexType>
<xs:sequence>
<xs:element minOccurs="0" name="returnValue" type="xs:string" />
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>

Because the .wsdl and .xsd files describe your Web service in a platform- agnostic manner, all the information clients need to connect and send requests to your service is contained within those two files. If you publish those files, clients on any platform can, with varying levels of difficulty depending on their tools, create a client for your service.

Creating the Metadata for the Web Service Client

Let s look at the process of creating an Indigo Web service client for an arbitrary Web service. Processing XML directly, in my opinion, is a pain. I don t want to do that. I d prefer that the framework encapsulate all the details of generating a Web service request, sending it, and parsing the response. In fact, I d like a managed class that looks and behaves like the actual service to the client but, when used, really sends messages to the actual service. A class is a bit of overkill ”an interface would be a better design choice.

Given that you have the .wsdl and .xsd definitions of a Web service, you can use the Wsdlgen.exe tool again to generate a source code file that contains a managed code definition for the Web service. You can then build the source code into an assembly that client code references, or you can include it as a source file in your client application.

Wsdlgen.exe uses .wsdl and .xsd files to create a source code definition of an interface you can use to call the service from a client application.

To create source code describing a Web service, obtain WSDL and any XSD that describes the service with which you want to have a conversation. Run the Wsdlgen utility using the following command:

wsdlgen <WSDL file> <any XSD files>

The following source code was generated by Wsdlgen.exe from the .wsdl and .xsd files that describe the TimeService Web service we previously created. I reformatted the code slightly to make it easier to read.

 // www_wiseowl_com.WebServices.cs 

//----------------------------------------------------------------------------
// <autogenerated>
// This code was generated by a tool.
// Runtime Version:1.2.30616.0
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </autogenerated>
//----------------------------------------------------------------------------

//
// This source code was auto-generated by WsdlGen, Version=1.2.30616.0.
//
using System.MessageBus.Services;

namespace www_wiseowl_com.WebServices {
[DatagramPortType( Namespace="http://www.wiseowl.com/WebServices")]
public interface ITime {
[ServiceMethod]
string GetUtcTimeAndDate(string culture);
}

[PortTypeChannel(Namespace="http://www.wiseowl.com/WebServices")]
public interface ITimeChannel : IDatagramPortTypeChannel {
[WrappedMessage(Namespace="http://www.wiseowl.com/WebServices")]
[ServiceMethod]
[return: WrappedMessage(Namespace="http://www.wiseowl.com/WebServices")]
string GetUtcTimeAndDate(string culture);
}
}

The prior source file has two noteworthy aspects. The Wsdlgen tool defines an interface, named ITime , that contains each service method implemented by the Web service. It also defines an interface, named ITimeChannel , which is the strongly typed channel you use when communicating with the Web service.

Calling the Web Service from a Client

To use these interfaces in a client application, your client application must do the following:

  • Call ServiceEnvironment.Load to acquire the default service environment from a configuration file.

  • Extract the ServiceManager object from the ServiceEnvironment .

  • Use the ServiceManager.CreateChannel method to create the channel interface that can connect to the Web service.

  • Reference the definition of the channel interface you produced using the Wsdlgen tool.

The following code is a client that uses the Time Web service:

 // client.cs 
using System;
using System.MessageBus;
using System.MessageBus.Services;
using www_wiseowl_com.WebServices; // The imported service namespace

public class Client {
public static void Main(string[] args) {
string culture = "en-US";
if (args.Length > 0) culture = args[0];

// Load the default service environment, called "main".
ServiceEnvironment se = null;

try {
se = ServiceEnvironment.Load();

// Retrieve the ServiceManager from the default environment
ServiceManager sm =
se[typeof(ServiceManager)] as ServiceManager;
if (sm == null)
throw new Exception ("ServiceManager is not available.");

// Start the service environment.
se.Open();

// Create a proxy channel that points to the service to call.
Uri uri = new Uri("soap.tcp://localhost:46000/TimeService/");
ITimeChannel channel = (ITimeChannel)
sm.CreateChannel(typeof(ITimeChannel), uri);

Console.WriteLine(channel.GetUtcTimeAndDate (culture));
}
catch (Exception e) {
Console.WriteLine (e);
}
finally {
if (se != null) se.Close();
}
}
}

As with the Web service itself, the client application can obtain its Indigo configuration information by asking the Framework to load the information from the application configuration file. I named the client application client.exe, so here is its associated client.exe.config configuration file.

 <configuration> 
<system.messagebus>
<serviceEnvironments>
<serviceEnvironment name="main">
<port>
<identityRole>soap.tcp://localhost:46001/TimeClient/</identityRole>
</port>
<!-- CAUTION: Security disabled for demonstration purposes only. -->
<remove name="securityManager" />
<policyManager>
<!-- CAUTION: Security disabled for demonstration purposes only. -->
<!-- Permits unsigned policy statements. Default requires signed policy statements -->
<areUntrustedPolicyAttachmentsAccepted>
true
</areUntrustedPolicyAttachmentsAccepted>
<isPolicyReturned>true</isPolicyReturned>
</policyManager>
</serviceEnvironment>
</serviceEnvironments>
</system.messagebus>
</configuration>

Note that I define a port and an identify role for the client. The identityRole element s value must be a unique identifier and allow a service to initiate communications with the client. In my example, I specify both the name and location of the client, which eliminates the need to specify available transports separately. Regardless, my client application doesn t use this server-callback capability.

Programmatic Configuration

I think placing most communications configuration information in a separate file from the application is the best approach for most scenarios. However, occasionally you might not know until runtime which service you want, or you might want to determine other configuration parameters dynamically. Indigo allows you to configure the runtime environment programmatically ”in fact, loading the configuration file really just causes the runtime itself to issue these programmatic requests.

The code is fairly self-explanatory, so I ll simply list the source for the programmatic versions of the Web service and client. Note that these changes don t affect the service itself; all the changes occur in the service s host application. Here s the source code for the host:

 // host.cs 
using System;
using System.Authorization;
using System.MessageBus;
using System.MessageBus.Policy;
using System.MessageBus.Security;
using System.MessageBus.Services;

class Host {
static void Main(string[] args) {
// Load and configure the default ServiceEnvironment.
ServiceEnvironment se = null;
try {
se = ServiceEnvironment.Load();
Port port = se [typeof(Port)] as Port;
port.IdentityRole = new Uri("soap.tcp://localhost:46000/TimeService/");

// Register the Time type as activatable.
ServiceManager sm = se [typeof(ServiceManager)] as ServiceManager;
sm.ActivatableServices.Add(typeof(WiseOwl.Time));

// Enable the PolicyManager to accept unsigned policy messages
// because this service does not have X509 certificates.
// For demonstration purposes only.
PolicyManager pm = se[typeof(PolicyManager)] as PolicyManager;
pm.AreUntrustedPolicyAttachmentsAccepted = true;
pm.IsPolicyReturned = true;

// Disable security for receiving messages.
// For demonstration purposes only.
SecurityManager secman = (SecurityManager)se[typeof(SecurityManager)];
secman.IsEnabledForReceive = false;

se.Open();
Console.WriteLine("Press enter to stop the services...");
Console.ReadLine();
}
finally {
if (se != null) se.Close();
}
}
}

And here s the source code for the client:

 // client.cs 
using System;
using System.Authorization;
using System.MessageBus;
using System.MessageBus.Policy;
using System.MessageBus.Security;
using System.MessageBus.Services;
using www_wiseowl_com.WebServices; // The imported service namespace

public class Client {
public static void Main(string[] args) {
string culture = "en-US";
if (args.Length > 0) culture = args[0];

// Load the default service environment, called "main".
ServiceEnvironment se = null;

try {
se = ServiceEnvironment.Load();

// Retrieve the ServiceManager from the default environment
ServiceManager sm =
se [typeof(ServiceManager)] as ServiceManager;
if (sm == null)
throw new Exception ("ServiceManager is not available.");

// Start the service environment programmatically.
Port port = se[typeof(Port)] as Port;
port.IdentityRole = new Uri("soap.tcp://localhost:46001/TimeClient/");

// Allow PolicyManager to accept unsigned policy messages because
// client does not have X509 certificates.
// CAUTION: Security disabled for demonstration purposes.
PolicyManager pm = se[typeof(PolicyManager)] as PolicyManager;
pm.AreUntrustedPolicyAttachmentsAccepted = true;
pm.IsPolicyReturned = true;

// Turn off access control.
// CAUTION: Security disabled for demonstration purposes.
SecurityManager secman = (SecurityManager) se[typeof(SecurityManager)];
secman.DefaultReceiverScope.AccessControl.AccessRequirementChoices.Add
(new AccessRequirementChoice());
secman.IsEnabledForReceive = false;

// Start the service environment.
se.Open();

// Create a proxy channel that points to the service to call.
Uri uri = new Uri("soap.tcp://localhost:46000/TimeService/");
ITimeChannel channel = (ITimeChannel)
sm.CreateChannel(typeof(ITimeChannel), uri);

Console.WriteLine(channel.GetUtcTimeAndDate (culture));
}
catch (Exception e) {
Console.WriteLine (e);
}
finally {
if (se != null) se.Close();
}
}
}



Introducing Microsoft WinFX
Introducing WinFX(TM) The Application Programming Interface for the Next Generation of Microsoft Windows Code Name Longhorn (Pro Developer)
ISBN: 0735620857
EAN: 2147483647
Year: 2004
Pages: 83
Authors: Brent Rector

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