Chapter 12. Creating the Communication and Integration Case

WHAT'S IN THIS CHAPTER?

  • Setting up a WCF solution for a communication and integration case

  • Configuring and working with WCF routing

  • Enabling a publish/subscribe pattern

  • Communicating with ASMX style webservices

  • Using the MSMQ binding

  • Implementing a REST based approach

This chapter is an interactive walkthrough about how to create a communication and integration case in Visual Studio 2010 using WCF. It starts with defining requirements for a case and shows how to develop the solution step by step. At the end of this chapter, you will have developed interfaces, hosts, and clients that interact with each other. This is a complete example — you test the process and see it working in action.

DEFINING THE REQUIREMENTS

A global U.S.-based company sells its products to customers worldwide. Orders are entered in the U.S. headquarters but the products are shipped out of warehouses all over the world (Belgium, Argentina, and so on). The company has to integrate existing applications into different branches worldwide for order entry, order tracking, and local delivery management where different types of clients and services (legacy and new) communicate to fulfill the order.

An order is created at the U.S. headquarters using an existing order entry application. This application is not using WCF as it was developed before WCF was available. It uses queued messaging based on MSMQ to send a message containing the data for the order to other applications.

This data has to be picked up by a service running at the U.S. headquarters which is responsible for first checking the validity of the order. This validation is a functionality that cannot be implemented in the legacy order entry client. An order is invalid if the products that are ordered are not available for the country where the order has to be delivered. This service figures out which products are available by calling another service for each country.

Another responsibility of this service is to add information to the message. As the order entry application was developed before the company started doing business on a global scale, the format of the ID of the product does not support a global business. The service has to add a prefix to these IDs, indicating the country where the product is in the warehouse.

Checking if the product is available for the country is done by yet another service. This service is an existing ASMX .NET 2.0 service that searches for the information on a small SQL Server-based database.

The service also has to add a localized description for each ordered product. This is done by calling a service which is on the intranet but does not act as a SOAP-enabled service. It's a REST-based service which answers with XML containing the localized description.

After the data is valid and complete it has to be sent to services at the correct local branch. This needs to be done via a routing service to quickly allow new branches in other countries to receive orders. In each branch the services have the same interface but they can use different protocols of communicating. Currently the branch in Argentina is using a wsHttpBinding and the branch in Belgium is using netTcpBinding. This is due to an internal firewall configuration aspect where the Argentina branch is not allowed to use the netTCP as protocol.

There is another application that wants to monitor the number of orders for each country. This application is always running and must show these metrics in real-time as in kiosk mode at the desktop of the CEO of the company. The CEO considers this a business activity that monitors and wants to see these metrics without clicking buttons. This application subscribes to events coming from the OrderEntry service.

See Figure 12-1 for a complete overview of the case.

FIGURE 12-1

Figure 12.1. FIGURE 12-1

SETTING UP THE SOLUTION

The complete solution consists of 13 projects. These projects are divided into 5 groups. This results in the following list of projects.

  1. Services

    • HQOrderEntryServiceHost: A console application as host for the OrderEntry service running at the headquarters.

    • HQOrderEntryImplementation: A class library containing the implementation of the operations for receiving orders; also to subscribe to the tracking information.

    • HQLocalizationService: A console application that hosts the localization service which is responsible for translating product descriptions. In this application the host, the interface, and the implementation of it are present together.

    • HQProductServiceASMX: A classic .NET 2.0 ASMX web service responsible for checking whether a product is available for a country.

    • RouterHost: A console application that exposes a service acting as the router.

  2. Service Interfaces

    • HQOrderEntryServiceInterface: A class library containing the interface describing the operation to receive the order.

    • LocalOrderEntryInterface: A class library containing the interface for the order entry services at the branches.

    • RealTimeOrderTrackingCallBackContract: A library containing a contract for the calls from the HQOrderEntryServiceHost to the tracking application.

  3. Clients

    • OrderEntryApplication: A Windows application for entering orders. This application sends the order as a message on a queue.

    • RealTimeOrderTrackingApplication: A Windows application that shows the number of orders for each country in real-time.

  4. Branches

    • BelgiumHost: A console application as host exposing the implementation of the LocalOrderEntryInterface for Belgium on a netTcpBinding.

    • ArgentinaHost: A console application as host exposing the implementation of the LocalOrderEntryInterface for Belgium on a wsHttpBinding.

  5. HelperLib

    • HelperLib: A class library containing functions for serialization and deserialization.

Start by creating a blank solution named CommunicationAndIntegrationCase and add five solution folders to the project (see Figure 12-2).

FIGURE 12-2

Figure 12.2. FIGURE 12-2

These solution folders are listed in Figure 12-3.

FIGURE 12-3

Figure 12.3. FIGURE 12-3

CREATING THE HQORDERENTRYINTERFACE

The interface for receiving an order is a WCF ServiceContract. You create this interface in a separate class library so it can be referenced from multiple applications.

Add a class library project to the solution folder called HQOrderEntryServiceInterface. Add reference to the System.ServiceModel and System.Runtime.Serialization libraries.

Add a class called HQOrderEntry. This class defines the structure of the order as a WCF service contract.

Add a using statement to make the attributes in the System.Runtime.Serialization namespace available.

using System.Runtime.Serialization;

Add the following code to the OrderEntry class:

[DataContract]
public class HQOrderEntry
{
    [DataMember]
    public String OrderEntryID { get; set; }
    [DataMember]
    public DateTime OrderEntryDate { get; set; }
    [DataMember]
    public Customer OrderCustomer { get; set; }
    [DataMember]
    public List<OrderedProducts> OrderOrderedProducts { get; set; }

}
[DataContract]
public class Customer
{
    [DataMember]
public string CustomerName { get; set; }
    [DataMember]
    public string CustomerAddressLine1 { get; set; }
    [DataMember]
    public string CustomerAddressLine2 { get; set; }
    [DataMember]
    public string CustomerCountryCode { get; set; }
}
[DataContract]
public class OrderedProducts
{
    [DataMember]
    public string ProductID { get; set; }
    [DataMember]
    public int Quantity { get; set; }
    [DataMember]
    public string ProductName { get; set; }
}

Add an interface to the project called IOrderEntryService and add the following using statements:

using System.ServiceModel;
using System.Runtime.Serialization;

Define the interface as follows:

[ServiceContract]
[ServiceKnownType(typeof(HQOrderEntryServiceInterface.HQOrderEntry))]
public interface IOrderEntryService
{
    [OperationContract(IsOneWay = true, Action = "*")]
    void SendOrderEntry(System.ServiceModel.MsmqIntegration.MsmqMessage
           <HQOrderEntryServiceInterface.HQOrderEntry> orderEntry);
}

This defines a method that will receive a MSMQ message and that message contains an OrderEntry. It also specifies that it's a one-way operation which is needed when working with queues. With the serviceKnownType, indicate that data can be expected in the message that can be serialized to an OrderEntry class.

CREATING THE HELPERLIB

Before continuing with the solution, you need to create a library with some general functions that are useful throughout the project. The HelperLib is a class library that is used from multiple projects. It has a static class called GenericSerializer with methods to serialize objects into strings and deserialize strings back into objects.

There are two ways to serialize and deserialize. The first way is to do this with the XmlSerializer which does not rely on WCF. This is needed for the OrderEntry application. The second way is using the DataContractSerializer part of WCF. You need code for both approaches.

First reference the System.Runtime.Serialization library from this project. Add a class called GenericSerializer.cs to the HelpLib project and add the following code.

The needed using statements are shown here:

using System.Xml;
using System.Xml.Serialization;
using System.IO;

using System.Runtime.Serialization;

The code for the functions is shown here:

public static class GenericSerializer<T>
{
    public static string Serialize(T p)
    {
        XmlSerializer ser = new XmlSerializer(typeof(T));
        StringWriter sw = new StringWriter();
        ser.Serialize(sw, p);

        return sw.ToString();
    }

    public static T Deserialize(string xml)
    {
        XmlSerializer ser = new XmlSerializer(typeof(T));
        StringReader sr = new StringReader(xml);

        return (T)ser.Deserialize(sr);
    }

    static public string SerializeDC(T o)
    {
        DataContractSerializer dataContractSerializer =
                new DataContractSerializer(typeof(T));

        StringWriter stringWriter = new StringWriter();

        XmlWriter xmlWriter = XmlWriter.Create(stringWriter);

        dataContractSerializer.WriteObject(xmlWriter, o);
        xmlWriter.Close();

        return (stringWriter.ToString());
    }

    static public T DeserializeDC(string Xml)
    {
        DataContractSerializer dataContractSerializer =
                new DataContractSerializer(typeof(T));

        StringReader stringReader = new StringReader(Xml);
XmlReader xmlReader = XmlReader.Create(stringReader);

        T obj = (T)dataContractSerializer.ReadObject(xmlReader);

        return obj;
    }
}

Code snippet CreatingtheCommunicationandIntegrationCase.zip

CREATING THE ORDERENTRYIMPLEMENTATION

Add a class library to the Services solution folder named HQOrderEntryImplementation. This library contains the implementation of the OrderEntryServiceInterface and will process the incoming messages on the queue. It will read the messages using the msmqIntegrationBinding which will be configured later when the HQOrderEntryServiceHost is created.

This class library needs references to the HQOrderEntryServiceInterface, the HelperLib, System.Runtimte.Serialization, and System.ServiceModel.

Add a class and name it HQOrderEntryService.cs. This class needs to implement the IOrderEntryService in the HQOrderEntryServiceInterface namespace. See the following code:

public class HQOrderEntryService : HQOrderEntryServiceInterface.IOrderEntryService
{

    public void SendOrderEntry
       (MsmqMessage<HQOrderEntryServiceInterface.HQOrderEntry> orderEntryMsg)
    {
       //Add code later
    }
}

CREATING THE HQORDERENTRYSERVICEHOST

This application hosts the OrderEntryService. Add a console application to the Service solution folder and name it HQOrderEntryServiceHost. This project needs references to the HQOrderEntryImplementation library and the OrderEntryServiceInterface library. It also needs references to System.ServiceModel and System.Runtime.Serialization.

In the program class, add the following using statements:

using System.Runtime.Serialization;
using System.ServiceModel;

In the main method, add a try catch handler and some console writeline statements to indicate the status of the service host. The last line is a readkey operation to make sure the host application stays open to process the messages.

Console.WriteLine("Started");
try
{
    //Add code later
    Console.WriteLine("OK");
}
catch (Exception ex)
{
    Console.WriteLine(ex.Message);
    if (ex.InnerException != null)
    {
        Console.WriteLine(ex.InnerException.Message);
    }
}
Console.ReadKey();

In the try block declare a variable of type ServiceHost, instantiate it referring to the HQOrderEntryService implementation, and open the host.

ServiceHost serviceHostOrderEntryService;
serviceHostOrderEntryService = newServiceHost(
      typeof(HQOrderEntryImplementation.HQOrderEntryService));
serviceHostOrderEntryService.Open();

Add an application configuration file to the project. In this configuration you need to add a service with the name set to HQOrderEntryImplementation.HQOrderEntryService.

The service has one endpoint with the following Address-Binding-Contract properties:

  • Address: msmq.formatname:DIRECT=OS:.private$OrderEntryQueue

  • Binding: MsmqIntegrationBinding

  • Contract: HQOrderEntryServiceInterface.IOrderEntryService

You also need to specify more detailed properties about the msmqIntegrationBinding. Set the exactlyOnce to false as a nontransactional queue will be used. For testing purposes, set the security mode to none so you do not need to activate the Active Directory integration. The app.config file should look like this:

<?xml version="1.0"?>
<configuration>
  <system.serviceModel>

    <services>
      <service name="HQOrderEntryImplementation.HQOrderEntryService">
        <endpoint
          address="msmq.formatname:DIRECT=OS:.private$OrderEntryQueue"
          binding="msmqIntegrationBinding"
          contract="HQOrderEntryServiceInterface.IOrderEntryService"
          bindingConfiguration="BindingMSMQ"/>
      </service>

    <bindings>
<msmqIntegrationBinding>
        <binding name="BindingMSMQ" exactlyOnce="false">
          <security mode="None"/>
        </binding>
      </msmqIntegrationBinding>
    </bindings>

  </system.serviceModel>

</configuration>

CREATING THE ORDERENTRYAPPLICATION

For the OrderEntryApplication, add a Windows forms application to the solution under the Client solution folder. Name it OrderEntryApplication.

This project needs references to the HelperLib, the HQOrderEntryInterface, and System.Messaging. System.Messaging contains the API to send a message to a MSMQ queue.

In the form, add a method called SendMessage with the following code. This method takes a string as parameter and sends this data to the queue. It instantiates a MessageQueue object for the OrderEntryQueue, instantiates a message, and constructs an XMLDocument. The InnerXML property of the XMLDocument is set to the data and the Body property of the message is set to this XMLDocument. After this the messageQueue sends the message.

private static void SendMessage(string data)
{
    MessageQueue messageQueue;
    messageQueue = new MessageQueue(@".Private$OrderEntryQueue");

    System.Messaging.Message message;
    message = new System.Messaging.Message();

    System.Xml.XmlDocument xmlDocument;
    xmlDocument = new XmlDocument();

    xmlDocument.InnerXml = data;

    message.Body = xmlDocument;
    messageQueue.Send(message);
}

This method needs to receive data in an XML format which contains the serialization of the message. To create this XML, add a function called GetXMLForOrderEntry. The function looks like this:

private static string GetXMLForOrderEntry ()
{
    string tmp;

    HQOrderEntryServiceInterface.HQOrderEntry test;
test = new HQOrderEntryServiceInterface.HQOrderEntry();

    test.OrderEntryID = "00000001";
    test.OrderEntryDate = System.DateTime.Now;
    test.OrderCustomer = new HQOrderEntryServiceInterface.Customer();
    test.OrderCustomer.CustomerName = "WROX";
    test.OrderCustomer.CustomerAddressLine1 = "CustomerAddressLine1";
    test.OrderCustomer.CustomerAddressLine2 = "CustomerAddressLine2";
    test.OrderCustomer.CustomerCountryCode = "BE";
    test.OrderOrderedProducts =
      new List<HQOrderEntryServiceInterface.OrderedProducts>();
    test.OrderOrderedProducts.Add(
      new HQOrderEntryServiceInterface.OrderedProducts()
      );
    test.OrderOrderedProducts[0].ProductID = "P08872";
    test.OrderOrderedProducts[0].Quantity = 5;
    test.OrderOrderedProducts[0].ProductName = "Car";
    test.OrderOrderedProducts.Add(
       new HQOrderEntryServiceInterface.OrderedProducts()
      );
    test.OrderOrderedProducts[1].ProductID = "P02287";
    test.OrderOrderedProducts[1].ProductName = "Bike";
    test.OrderOrderedProducts[1].Quantity = 5;

    tmp = HelperLib.GenericSerializer<HQOrderEntryServiceInterface.HQOrderEntry>
                      .Serialize(test);

    return tmp;
}

The function instantiates an OrderEntry class, sets the values of the members of this class to some meaningful values, and then uses the Serialize function in the HelperLib to obtain the XML.

Now you can add a button to the form which will execute the following code:

SendMessage(GetXMLForOrderEntry());

You can also experiment with more buttons sending messages with other content to the queue.

CREATING THE LOCALORDERENTRYINTERFACE

Add a class library project. Name it LocalOrderEntryInterface and add an interface called IReceiveOrderEntryLocalBranch. This interface contains the service contract for the local OrderEntry services at the different branches and defines the datacontract for the local OrderEntry.

First add a reference to the System.ServiceModel and System.Runtime.Serialization libraries.

Add an interface to this project called IReceiveOrderEntryLocalBranch.cs.

In this file, add the needed using statements:

using System.Runtime.Serialization;
using System.ServiceModel;

Add the following code:

[ServiceContract]
public interface IReceiveOrderEntryLocalBranch
{
    [OperationContract]
    int SendLocalOrderEntry(LocalOrderEntry localOrderEntry);
}

[DataContract]
public class LocalOrderEntry
{
    [DataMember]
    public String OrderEntryID { get; set; }
    [DataMember]
    public DateTime OrderEntryDate { get; set; }
    [DataMember]
    public string CustomerName { get; set; }
    [DataMember]
    public string CustomerAddressLine1 { get; set; }
    [DataMember]
    public string CustomerAddressLine2 { get; set; }
    [DataMember]
    public string CustomerCountryCode { get; set; }
    [DataMember]
    public List<OrderedProducts> OrderOrderedProducts { get; set; }
}

[DataContract]
public class OrderedProducts
{
    [DataMember]
    public string ProductID { get; set; }
    [DataMember]
    public int Quantity { get; set; }
    [DataMember]
    public string LocalizedDescription { get; set; }
}

Code snippet CreatingtheCommunicationandIntegrationCase.zip

CONTINUING THE ORDERENTRYIMPLEMENTATION

You need to implement the flow of the process for an order received by the SendOrderEntry method. When the message is received it is checked to see if the order is valid. If considered valid, it is converted to the localOrderEntry structure. This conversion will also take care of the translation of the product descriptions. When the order is not valid it is sent back to a queue.

The OrderEntryService library needs references to the HelperLib, the LocalOrderEntryInterface, the OrderEntryInterface, System.ServiceModel, System.Runtime.Serialization, and System.Transactions.

Add the following method stubs to the HQOrderEntryService.cs file in the OrderEntryImplementation project. These will be stubs for now; later you will add the needed code. You need method stubs for checking whether the order is valid, converting the schema, routing the order entry, and sending the order entry to the invalid queue.

private bool CheckIfOrderIsValid
             (HQOrderEntryServiceInterface.HQOrderEntryorderEntry)
 {
    //Add code later
    return true;
}

private LocalOrderEntryInterface.LocalOrderEntry
     ConvertOrderEntrySchema(HQOrderEntryServiceInterface.HQOrderEntry orderEntry)
 {
    //Add code later
    return null;
}

private void RouteOrderEntry
            (LocalOrderEntryInterface.LocalOrderEntry localOrderEntry)
 {
    //Add code later
 }

private void SendToInvalidOrderQueue
            (MsmqMessage<HQOrderEntryServiceInterface.HQOrderEntry> orderEntryMsg)
 {
    //Add code later
 }

private string TranslateProductDescription(string productID, string languageCode)
 {
    //Add code later
    return "";
 }

After creating these stubs you can write the process flow in the SendOrderEntry method:

public void SendOrderEntry(
            MsmqMessage<HQOrderEntryServiceInterface.HQOrderEntry> orderEntryMsg)
{
    try
    {
        if (CheckIfOrderIsValid(orderEntryMsg.Body))
        {
            RouteOrderEntry(ConvertOrderEntrySchema(orderEntryMsg.Body));
        }
        else
        {
            SendToInvalidOrderQueue(orderEntryMsg);
        }
    }
catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
    }
}

CREATING THE HQPRODUCTSERVICEASMX

There are several things you need to do to create the HQProductServiceASMX. First you must create the web service. Then you need to add it as a service reference to the implementation. Finally you need to create the CheckIfOrderIsValid, TranslateProductDescription, and ConvertOrderEntrySchema methods.

Creating the Web Service

The order is valid if all ordered products can be delivered to the country of the customer. This check is done by an ASMX web service based on the productId and the countryCode of the customer.

Add a new web site to the Services solution folder. You can do this by right-clicking the solution folder and selecting Add . . . New Website. In the Add New Web Site dialog box, select ASP.NET Web Service. See Figure 12-4.

FIGURE 12-4

Figure 12.4. FIGURE 12-4

Rename the file called Service.asmx in ProductService.asmx and open the Service.cs file in the App_Code directory. In this file, delete the HelloWorld method and add code for a method called IsProductAvailableForCountry. This method takes a productID and a countryCode as parameters and returns a Boolean.

[WebMethod]
public bool IsProductAvailableForCountry(string productID, string countryCode)
{
}

A hardcoded approach is needed to implement this method. Instead of checking whether the database of the product is deliverable for the country, you return true or false based on the country. If the countryCode is BE or AR, consider it valid. Otherwise, it is not valid. You can implement the method like this:

[WebMethod]
public bool IsProductAvailableForCountry(string productID, string countryCode)
{

    if ((countryCode == "BE") || (countryCode == "AR") || (countryCode == "AT"))
    {
        return true;
    }
    else
    {
        return false;
    }
}

You need to configure this web site to not use dynamic ports but use a static port number. Use port number 8081 for this. Open the Properties window of the web site. You can do this by pressing F4 on the projectname. Set Use Dynamic Ports to False and set the Port Number to 8081. See Figure 12-5.

FIGURE 12-5

Figure 12.5. FIGURE 12-5

Adding the HQProductServiceASMX as Service Reference to the OrderEntryServiceImplementation

Add a service reference in the HQOrderEntryImplementation project.

Right-click the references and select Add Service Reference. This opens the Add Service Reference dialog box. Click the Discover button. You should see the operation in HQProductService. See Figure 12-6.

FIGURE 12-6

Figure 12.6. FIGURE 12-6

Set the namespace to HQProductServiceASMXClient and click OK.

This action will generate an app.config file in the HQOrderEntryImplementation project. You can delete this file as you are going to add configuration to the HQOrderEntryServiceHost.

Coding the CheckIfOrderIsValid Method

Now the HQProductService is added as a reference so you can develop the CheckIfOrderIsValid method like this:

private bool CheckIfOrderIsValid
               (HQOrderEntryServiceInterface.HQOrderEntry orderEntry)
{

    HQProductServiceASMXClient.ServiceSoapClient client;
    client = new HQProductServiceASMXClient.ServiceSoapClient();

    bool orderIsValid;
    orderIsValid = true;

    foreach (var item in orderEntry.OrderOrderedProducts)
{
        orderIsValid = client.IsProductAvailableForCountry(
                                item.ProductID,
                                orderEntry.OrderCustomer.CustomerCountryCode);
    }
    return orderIsValid;
}

Coding the TranslateProductDescription Method

For accessing the TranslateProductDescription service via REST there's no need to add a service reference or to create proxies. You need to use an HttpWebRequest and specify the languageCode and productID as part of the URL to reach the service. The service responds with a string, serialized as a WCF datacontract containing the translation. You need to deserialize this with the DeserializeDC method from the HelperLib.

private string TranslateProductDescription(string productID, string languageCode)
{

    System.Net.HttpWebRequest webrequest;
    webrequest = (System.Net.HttpWebRequest)System.Net.HttpWebRequest.Create(
                   string.Format
                    (@"http://localhost:8081/HQLocalizationService/
                     TranslateProductDescriptions/{0}/{1}",
                      languageCode,
                      productID));
    webrequest.ContentLength = 0;

    System.Net.HttpWebResponse webresponse;

    webresponse = (System.Net.HttpWebResponse)webrequest.GetResponse();

    Encoding enc = System.Text.Encoding.GetEncoding(1252);
    System.IO.StreamReader loResponseStream = new
           System.IO.StreamReader(webresponse.GetResponseStream(), enc);

    string response = loResponseStream.ReadToEnd();

    string answer;
    answer = GenericSerializer<string>.DeserializeDC(response);

    return answer;

}

Coding the ConvertOrderEntrySchema Method

This method takes an HQOrderEntry as input and results in a LocalOrderEntry. The method creates a new LocalOrderEntry and fills it with the data from the HQOrderEntry. It loops through all the products in the order, calls the TranslateProductDescription for each product, and adds it together with the country prefix to the local order.

To have access to the LocalOrderEntry class, you need to add a reference to LocalOrderEntryInterface project first.

private LocalOrderEntryInterface.LocalOrderEntry
         ConvertOrderEntrySchema(
              HQOrderEntryServiceInterface.HQOrderEntry orderEntry)
{

    LocalOrderEntryInterface.LocalOrderEntry localOrderEntry;
    localOrderEntry = new LocalOrderEntryInterface.LocalOrderEntry();

    localOrderEntry.CustomerName =
              orderEntry.OrderCustomer.CustomerName;
    localOrderEntry.CustomerAddressLine1 =
              orderEntry.OrderCustomer.CustomerAddressLine1;
    localOrderEntry.CustomerAddressLine2 =
              orderEntry.OrderCustomer.CustomerAddressLine2;
    localOrderEntry.CustomerCountryCode =
              orderEntry.OrderCustomer.CustomerCountryCode;

    localOrderEntry.OrderOrderedProducts =
              new List<LocalOrderEntryInterface.OrderedProducts>();

    foreach (var item in orderEntry.OrderOrderedProducts)
    {
        string translation;
        translation = TranslateProductDescription(item.ProductID,
                        orderEntry.OrderCustomer.CustomerCountryCode);

        localOrderEntry.OrderOrderedProducts.Add(
                      new LocalOrderEntryInterface.OrderedProducts
                      { ProductID = orderEntry.OrderCustomer.CustomerCountryCode
                                    + "/"
                                    + item.ProductID, Quantity = item.Quantity,
                        LocalizedDescription = translation });
    }
    return localOrderEntry;

}

Code snippet CreatingtheCommunicationandIntegrationCase.zip

CREATING THE HQLOCALIZATIONSERVICE

This service is a REST service exposed by WCF and is hosted in a console application. This means you are not using the standard WCF ServiceHost or IIS as host. Instead, use the WebServiceHost. You will not separate the interfaces, implementation, and host in three projects. As you are using a REST approach there is no reason to have the interfaces used by any other applications than the service itself.

Add a console application to the solution in the Services solution folder and give it references to System.Runtime.Serialization, System.ServiceModel, and System.ServiceModel.Web.

Add an interface called ITranslateProductDescriptions.cs. This file needs a using statement for the System.ServiceModel.Web namespace and has the following code:

[ServiceContract]
public interface ITranslateProductDescriptions
{
    [OperationContract]
    [WebGet(UriTemplate = @"/{languageCode}/{productID}")]
    string GetProductDescription(string productID, string languageCode);
}

Here you define a method called GetProductDescription which takes the two needed parameters to answer with a translation. On top of this method you should place a WebGet attribute indicating the URI template. This template will map the data found in the URL to the input parameters of the method.

Add a class for the implementation of this interface. For now this code will not really do the translation but will simply return a hardcode string:

public class TranslateProductDescriptions : ITranslateProductDescriptions
{

    public string GetProductDescription(string productID, string languageCode)
    {
        return "Translated";
    }
}

To start this host and open the URL you need to add the following code to the main method of the console application:

Console.WriteLine("TranslateProductDescriptions");
WebServiceHost webServiceHost;
webServiceHost = new WebServiceHost(typeof(TranslateProductDescriptions));
webServiceHost.Open();

Console.ReadKey();

You also need a using statement for System.ServiceModel.Web. Configure this host as follows:

<?xml version="1.0"?>
<configuration>
  <system.serviceModel>
    <services>
      <service name="TranslateProductDescriptions">
        <endpoint
            address=
       "http://localhost:8082/HQLocalizationService/TranslateProductDescriptions"
            binding="webHttpBinding"
            contract="ITranslateProductDescriptions" />
      </service>
    </services>
  </system.serviceModel>
</configuration>

Here you use the webHttpBinding as binding for the service.

CODING THE ROUTERORDERENTRY METHOD

Now that you have the method to convert the order entry schema to the local order entry ready, you can develop the method to route the order entry further to the destination. For this you need to first create a proxy for the IReceiveOrderEntryLocalBranch interface. Add a file called LocalOrderEntryProxy.cs to the HQOrderEntryImplementation project and add the needed include statements for System.ServiceModel. Insert the following code :

public class LocalOrderEntryProxy :
              ClientBase<LocalOrderEntryInterface.IReceiveOrderEntryLocalBranch>,
              LocalOrderEntryInterface.IReceiveOrderEntryLocalBranch
{
    public LocalOrderEntryProxy()
        : base("LocalOrderEntryEndpoint")
    {

    }
    public int SendLocalOrderEntry(
                LocalOrderEntryInterface.LocalOrderEntry localOrderEntry)
    {
        return Channel.SendLocalOrderEntry(localOrderEntry);
    }
}

This proxy is a class inheriting from ClientBase<T> and is implementing the IReceiveOrderEntryLocalBranch interface. This interface is also the generic type for the base class. The constructor of the class gets a string that is the name of the endpoint name in the configuration. The SendLocalOrderEntry method is just sending the localOrderEntry data to the channel by calling the method with the same name of the channel property of ClientBase.

With this proxy in place, you can now change the RouteOrderEntry method to HQOrderEntryService.cs like this :

private void RouteOrderEntry
               (LocalOrderEntryInterface.LocalOrderEntry localOrderEntry)
{
    try
    {
        LocalOrderEntryProxy localOrderEntryProxy;
        localOrderEntryProxy = new LocalOrderEntryProxy();
        int a;
        a = localOrderEntryProxy.SendLocalOrderEntry(localOrderEntry);
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
    }
}

CREATING THE REALTIMEORDERTRACKINGAPPLICATION

The RealTimeOrderTrackingApplication is an application that receives a signal for every order that was processed by the HQOrderEntry service. It displays the number of orders for each country it receives. To realize this you use a pub-sub pattern. The application subscribes itself to publications coming from the HQOrderEntryService. Subscribing is done by calling an operation that is exposed by a subscription service hosted by the HQServiceHost. When this operation is called the service remembers the caller. It does this by storing the reference to the callback channel in a list in memory at the service. When an order is placed the service calls a method on this callback channel. This method is part of a callback interface that is known by the service and is implemented at the caller. This means that at the moment of publication, the OrderEntry Service is acting as client and sends data to a service which is implemented at the tracking application.

First defined is the callback contract which is placed in a separate project.

Add a new class library called RealTimeOrderTrackingCallBackContract to the solution in the ServiceIntefaces solution folder. Add the System.ServiceModel library as reference and add an interface called IOrderTracking with the following code:

[ServiceContract]
    public interface IOrderTracking
    {
        [OperationContract]
        void NewOrderForCountry(string countryID);
    }

Coding the RealTimeOrderTrackingApplication Method

This application will be a Windows application. Add this application called RealTimeOrderTrackingApplication to the solution in the Clients solution folder. The application needs a reference to System.ServiceModel and to the RealTimeOrderTrackingCallBackContract.

You create the implementation of the RealTimeOrderTrackingCallBackContract by adding a class called CallBackImplementation. Create the class like this:

public class CallBackImplementation :
                 RealTimeOrderTrackingCallBackContract.IOrderTracking
{
    Dictionary<string,int> NumberOfOrderEntries;

    public CallBackImplementation()
    {
        NumberOfOrderEntries = new Dictionary<string, int>();

    }

    public void NewOrderForCountry(string countryID)
    {
    }
}

The constructor of this class instantiates a dictionary of string and int. This is needed to store the numbers of orders for each country. In this generic list the string is the country code and the int is the number of orders.

In the NewOrderForCountry method, you could show the number of order entries on a listbox on the form or update a chart. As this is more of a UI aspect, it is not included in the code for this book.

Adding the ISubscribeToOrderTrackingInfo Interface

For the subscription to the order tracking signal you need to have an interface with methods to subscribe and unsubscribe. This interface is placed in the HQOrderEntryServiceInterface project. This interface must also be aware of the CallbackContract, so first add a reference to the RealTimeOrderTrackingCallBackContract to the HQOrderEntryServiceInterface project.

Add an interface called ISubscribeToOrderTrackingInfo and add the needed namespaces:

using System.ServiceModel;
using System.Runtime.Serialization;

The interface looks like this:

[ServiceContract(CallbackContract =
                   typeof(RealTimeOrderTrackingCallBackContract.IOrderTracking))]
public interface ISubscribeToOrderTrackingInfo
{
    [OperationContract]
    void Subscribe();

    [OperationContract]
    void UnSubscribe();
}

This interface is like a regular service contract and uses the CallBackContract parameter of the ServiceContract attribute to refer to the callback contract.

Implementing the SubscribeService Method

The implementation of this service goes into the HQOrderEntryServiceImplementation project. Switch to this project and add a reference to the RealTimeOrderTrackingCallBackContract library. Next add a class called SubscribeService and let this class implement the ISubscribeToOrderTrackingInfo interface. The implementation looks like this:

[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
public class SubscribeService :
                  OrderEntryServiceInterface.ISubscribeToOrderTrackingInfo
{

    List<RealTimeOrderTrackingCallBackContract.IOrderTracking> callBacks;

    public SubscribeService()
{
       callBacks =
         new List<RealTimeOrderTrackingCallBackContract.IOrderTracking>();
    }
    public void Subscribe()
    {
        Console.WriteLine("**Someone Subscribed");
        callBacks.Add(
                   System.ServiceModel.OperationContext.Current
                    .GetCallbackChannel
                       <RealTimeOrderTrackingCallBackContract.IOrderTracking>());
    }

    public void PublishOrderEntrySignal(string countryID)
    {
        foreach (var item in callBacks)
        {
            item.NewOrderForCountry(countryID);
        }
    }

    public void UnSubscribe()
    {
        Console.WriteLine("**Someone UnSubscribed");
        callBacks.Remove(
                   System.ServiceModel.OperationContext.Current.
                   GetCallbackChannel
                     <RealTimeOrderTrackingCallBackContract.IOrderTracking>());
    }
}

Code snippet CreatingtheCommunicationandIntegrationCase.zip

Here you see that the service is going to run in the Single InstanceContextMode. This means there will be only one instantiation at the service and that this instantiation will be there before the first call and will be kept in memory after this call. This is needed because you want to remember the list of callback channels.

The list of subscribers is a list of references to the callback interface (IOrderTracking). When the subscribe method is called WCF, you have access to the callbackchannel by using the OperationContext class. This class is a static one and can get you the callback channel. This callback channel is of type IOrderTracking and is added to the list. Unsubscribing is just a matter of removing it from the list.

You also have a function called PublishOrderEntrySignal which takes the country ID as parameter and loops through all subscribed callback channels, and calls the NewOrderForCountry method.

Calling the Subscribers When the Order Is Processed

When an order entry is processed, you need to call all the callback channels. This can be done by calling the PublishOrderEntrySignal in the SubscribeService. Therefore you need access to the SubscribeService from within the implementation of the SendOrderEntry operation. To do this you need to use another class with a static reference to the SubscribeService. Add a class called SubscriberSingleton with the following code:

public class SubscriberServiceSingleton
{
    private static SubscribeService instance;
    private static readonly object singletonLock = new object();


    public static SubscribeService GetInstance()
    {
        if (instance == null)
        {
            lock (singletonLock)
            {
                if (instance == null)
                {
                    instance = new SubscribeService();
                }
            }
        }
        return instance;
    }
}

This class has a method to get the instance of the SubscribeService. When this method is called, it instantiates the SubscribeService if needed, and returns the instance.

As the GetInstance method is public and static it can be called from the HQOrderEntry service. Now you can add the call to publish the signal to the implementation of the SendOrderEntry method, like this:

if (CheckIfOrderIsValid(orderEntryMsg.Body))
{
    Console.WriteLine("Order Is VALID");
    RouteOrderEntry(ConvertOrderEntrySchema(orderEntryMsg.Body));
    HQOrderEntryImplementation.SubscriberServiceSingleton
    .GetInstance().PublishOrderEntrySignal(
                orderEntryMsg.Body.OrderCustomer.CustomerCountryCode);
}
else
{
    Console.WriteLine("Order Is not VALID");
    SendToInvalidOrderQueue(orderEntryMsg);
}

Opening the SubscribeService

You need to open this service in the main method of the HQOrderEntryServiceHost. In this code you instantiate the SubscribeService by using the SubscribeServiceSingleton class. The reference to the service is given to the service host at construction time. This results in the SubscribeService being available in all services hosted by this application:

ServiceHost serviceHostSubscribeService;
OrderEntryServiceImplementation.SubscribeService subscribeService;
subscribeService = HQOrderEntryImplementation.SubscriberServiceSingleton.
GetInstance();
serviceHostSubscribeService = new ServiceHost(subscribeService);
serviceHostSubscribeService.Open();

Subscribing from the RealTimeOrderTrackingApplication

This application needs a reference to the HQOrderEntryServiceInterface.

Add a button on the form that will start the subscription. The code behind this button is instantiating the CallBackImplementation class and wraps it into an InstanceContext:

CallBackImplementation callBack;
callBack = new CallBackImplementation();
InstanceContext instanceContext = new InstanceContext(callBack);

Then use a ChannelFactory of the ISubscribeToOrderTrackingInfo interface. This is done by using the DuplexChannelFactory, which takes the instanceContext wrapping the callbackimplementation as a parameter together with a NetTcpBinding:

ChannelFactory<OrderEntryServiceInterface.ISubscribeToOrderTrackingInfo> cf
   = new DuplexChannelFactory
           <OrderEntryServiceInterface.ISubscribeToOrderTrackingInfo>
             (instanceContext, new System.ServiceModel.NetTcpBinding());
HQOrderEntryServiceInterface.ISubscribeToOrderTrackingInfo subscriber
   = cf.CreateChannel(new EndpointAddress("net.tcp://localhost:9875"));
subscriber.Subscribe();

After the channel is created using the correct address you can call the subscribe method.

Configuring the HQOrderEntryServiceHost

The HQOrderEntryServiceHost is now hosting two services and has one client endpoint. The complete configuration becomes the following:

<configuration>
  <system.serviceModel>

    <services>
      <service name="HQOrderEntryImplementation.HQOrderEntryService">
        <endpoint
          address="msmq.formatname:DIRECT=OS:.private$OrderEntryQueue"
          binding="msmqIntegrationBinding"
          contract="HQOrderEntryServiceInterface.IOrderEntryService"
          bindingConfiguration="BindingMSMQ"/>


      </service>
      <service name="HQOrderEntryImplementation.SubscribeService">
<endpoint
          address="net.tcp://localhost:9875"
          binding="netTcpBinding"
          contract="HQOrderEntryServiceInterface.ISubscribeToOrderTrackingInfo"/>
      </service>
    </services>

    <bindings>
      <msmqIntegrationBinding>
        <binding name="BindingMSMQ" exactlyOnce="false">
          <security mode="None"/>
        </binding>
      </msmqIntegrationBinding>
    </bindings>
    <client>

      <endpoint
        address="http://localhost:8081/HQProductServiceASMX/HQProductService.asmx"
        binding="basicHttpBinding"
        contract="HQProductServiceASMXClient.ServiceSoap"
        name="ServiceSoap" />

    </client>

  </system.serviceModel>

</configuration>

Code snippet CreatingtheCommunicationandIntegrationCase.zip

CREATING THE ROUTER

For the router you need another service host application. This host will not implement a functional interface but rather a technical interface which is part of the WCF ServiceModel.Routing namespace. Instead of executing code, the router will send the incoming message to another service based on filters defined in the configuration.

Add a console application named RouterHost to the services solution folder and give it references to System.ServiceModel and System.ServiceModel.Routing.

The code in the main method for opening the router is this:

try
{
    Console.WriteLine("RoutingService");
    ServiceHost serviceHost;
    serviceHost = new ServiceHost
        (typeof(System.ServiceModel.Routing.RoutingService));
    serviceHost.Open();
    Console.WriteLine("Started");
}
catch (Exception ex)
{
    Console.WriteLine(ex.Message);
}

Instead of giving the type of an implementation you just give the type of System.ServiceModel.Routing.RoutingService.

All the magic happens in configuration. First add a service like this:

<services>
  <service
      behaviorConfiguration="RoutingServiceBehavior"
      name="System.ServiceModel.Routing.RoutingService">
    <endpoint
      address="http://localhost:9874/Router"
      binding="wsHttpBinding"
      name="RoutingServiceEndpoint"
      contract="System.ServiceModel.Routing.IRequestReplyRouter" />
  </service>
</services>

Here you specify that the service you are hosting is called System.ServiceModel.Routing.RoutingService with System.ServiceModel.Routing.IRequestReplyRouter as contract. You also specify the address and the binding.

This service has a behaviorConfiguration called RoutingServiceBehavior where there is a routing element referring to a filterTableName called routingRules.

<behaviors>
  <serviceBehaviors>
    <behavior name="RoutingServiceBehavior">
      <serviceDebug includeExceptionDetailInFaults="true" />
      <routing filterTableName="routingRules" routeOnHeadersOnly="false" />
    </behavior>
  </serviceBehaviors>
</behaviors>

This filterTableName refers to another section in the configuration where two filters are defined. Both filters are defined as XPath. The first one filters out the messages where the text of the country code is AR. The second one filters out the message for BE. Both filters have a name which refers back to the routingRules where the two names of the filters are mapped to endpoints:

<routing>
  <filters>
    <filter name="Filter_CustomerCountryCode_AR"
            filterType="XPath"
            filterData=
             "boolean(//*[local-name()= 'CustomerCountryCode']/text() = 'AR')"
     />
    <filter name="Filter_CustomerCountryCode_BE"
            filterType="XPath"
filterData=
             "boolean(//*[local-name()= 'CustomerCountryCode']/text() = 'BE')"
    />
  </filters>
  <filterTables>
    <filterTable name="routingRules">
      <add filterName="Filter_CustomerCountryCode_AR"
           endpointName="ArgentinaEndpoint"/>
      <add filterName="Filter_CustomerCountryCode_BE"
           endpointName="BelgiumEndpoint"/>
    </filterTable>
  </filterTables>

</routing>

The endpoints are normal endpoints and have their addresses, bindings, and contracts:

<client>
  <endpoint
      binding="wsHttpBinding"
      bindingConfiguration=""
      contract="*"
      address="http://localhost:9874/ArgentinaHost"
      name="ArgentinaEndpoint" kind=""
      endpointConfiguration="">

  </endpoint>
  <endpoint
     binding="netTcpBinding"
     bindingConfiguration=""
     contract="*"
     address="net.tcp://localhost:9871/BelgiumBranch"
     name="BelgiumEndpoint" kind=""
     endpointConfiguration="">

  </endpoint>

</client>

CONFIGURING THE HQORDERENTRYSERVICEHOST

To make the complete solution work you need to take care of the correct configuration of the HQOrderEntryServiceHost. This configuration has two services: one for receiving order entries from the queue and one for the subscription service. There are also two clients: one to reach the ASMX service to check the availability of the order for a country, and another to reach the router.

This is the complete configuration:

<configuration>
  <system.serviceModel>

    <services>
<service name="HQOrderEntryImplementation.HQOrderEntryService">
        <endpoint
          address="msmq.formatname:DIRECT=OS:.private$OrderEntryQueue"
          binding="msmqIntegrationBinding"
          contract="HQOrderEntryServiceInterface.IOrderEntryService"
          bindingConfiguration="BindingMSMQ"/>


      </service>
      <service name="HQOrderEntryImplementation.SubscribeService">
        <endpoint
          address="net.tcp://localhost:9875"
          binding="netTcpBinding"
          contract="HQOrderEntryServiceInterface.ISubscribeToOrderTrackingInfo"/>
      </service>
    </services>

    <bindings>
      <msmqIntegrationBinding>
        <binding name="BindingMSMQ" exactlyOnce="false">
          <security mode="None"/>
        </binding>
      </msmqIntegrationBinding>
    </bindings>
    <client>

      <endpoint
          address=
           "http://localhost:8081/HQProductServiceASMX/HQProductService.asmx"
          binding="basicHttpBinding"
          contract="HQProductServiceASMXClient.ServiceSoap"
          name="ServiceSoap" />
      <endpoint
        name="LocalOrderEntryEndpoint"
        address="http://localhost:9874/Router"
        binding="wsHttpBinding"
        contract="LocalOrderEntryInterface.IReceiveOrderEntryLocalBranch" />
    </client>

  </system.serviceModel>
</configuration>

Code snippet CreatingtheCommunicationandIntegrationCase.zip

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.118.137.7