It's 2008. Where's my flying car? RSS 2.0
 Tuesday, June 07, 2005

Here’s the raw, not really well documented code drop with the sample and framework code for my CSI360 and CSI359 talks here at TechEd US. The talks are today at 5pm (CSI360 – Asynchronous Messaging) and on Thursday at 1:30pm (CSI359 – Handling Transaction Abort Cases). As soon as I find time, I’ll document the framework classes a bit better here on the blog. The archive contains, amongst other things, a WSE channel and a WebRequest/WebResponse set that lets you use MSMQ as an alternate transport for WSE and/or ASMX. It also has the complete queue listener code for the messaging series I posted some months ago.

My blogging backlog is ridiculous. In the past weeks I’ve crossed the Atlantic several times (with one quick trip to Singapore in addition to that), had some crazy “one city per day” trips and had to meet deadlines for whitepapers, articles, and presentations. I guess I travel too much. From here (Orlando,FL) I will fly straight to the Pakistan Developer Conference in Karachi (about 24 hours, via Amsterdam and Dubai) and then back home. If all goes well, I’ll be at home for 2 weeks. That’s a first for this year, I think.

Download: techEd2005.zip

Tuesday, June 07, 2005 12:07:03 PM (Pacific Daylight Time, UTC-07:00)  #    Comments [0] - Trackback
Talks
 Monday, May 02, 2005

In the past months I’ve been throwing ideas back and forth with some of my friends and we’re slowly realizing that “Service Oriented Architecture” doesn’t really exist.

The term “Service Oriented Architecture” implies that there is something special about architecture when it comes to service orientation, Web services, XML, loose coupling and all the wonderful blessings of the past 5 years in this wave. But if you look at it, there really isn’t much special about the good, old, proven architectural principles once you throw services into the picture.

I’ll try to explain what I mean. There are five pillars of software architecture (this deserves more elaboration, but I will keep it short for now):

·        Edges: Everything that talks about how the network edge of a software system is shaped, designed, and implemented. SOAP, WSDL, WS-*, IIOP, RMI, DCOM are at home here, along with API and message design and ideas about coupling, versioning, and interoperability.

·        Protocols: Which information do you exchange between two layers of a system or between systems and how is that communication shaped? What are the communication patterns, what are the rules of communication? There are low-level protocols that are technically motivated, there are high-level protocols that are about punting business documents around. Whether you render a security token as a binary thing in DCOM or as an angle brackets thing is an edge concern. The fact that you do and when and in which context is a protocol thing. Each protocol can theoretically be implemented on any type of edge. If you were completely insane, you could implement TCP on top of SOAP and WS-Addressing and some other transport.

·        Runtimes: How do you implement a protocol? You pick an appropriate runtime, existing class or function libraries, and a programming language. That’s an architectural decision, really. There are good reasons why people pick C#, Java, Visual Basic, or FORTRAN, and not all of them are purely technical. Technically, the choice of a runtime and language is orthogonal to the choice of a protocol and the edge technology/design. That’s why I list it as another pillar. You could choose to do everything in Itanium assembly language and start from scratch. Theoretically, nothing stops you from doing that, it’s just not very pragmatic.

·        Control Flow: For a protocol to work and really for any program to work, you need concepts like uni- and bidirectional communication and their flavors such as datagrams, sockets, and queues, which support communication styles such as monologues, dialogues, multicast, or broadcast. You need to ideas like parallelization and synchronization, and iterations and sequences. All of these are abstract ideas. You can implement those on any runtime. They are not dependent on a special edge. They support protocols, but don’t require them. Another pillar.

·        State: This is why we write software (most of it, at least). We write software to transform a system from one state to the next. Press the trigger button and a monster in Halo turns into a meatloaf, and you score. Send a message to a banking system and $100.000 change owners. Keeping track of state, keeping it isolated, current, and consistent or things to consider. Is it ok to have it far away or do you need it close by? Do you cache, and replicate it for the purpose? Is it reference data or business data? Consolidated, preprocessed, or raw? How many concurrent clients have access to the data and how do you deal with the concurrency? All these are questions that have to do with state, and only state. None of this is depends on having a special technology that is being talked through way up above at the edge.

Service orientation only speaks about the edge. Its tenets are about loose coupling, about independent evolution and versioning of contracts, and about technology-agnostic metadata exchange. All this is important to make systems interoperate better and to create systems where the effects of changes to one of its parts to any other part are minimized.

But none of the SO tenets really speaks about architecture [Sidenote: The “autonomy” is about autonomous development teams and not about autonomous computing]. When you look at what’s being advertised as “serviced oriented architecture”, you see either the marketing-glorified repackaging of Ethernet, TCP/IP, and LDAP (“Enterprise Service Bus”), or architectural blueprints that looks strikingly similar to things that people have been doing for a long time with DCE, CORBA, J2EE, COM, or mainframe technologies. What’s different now is that it is easier, cheaper and likely more productive to create bridges between systems. And even that comes at a significant price at this point. Realistically, the (web) services stacks yet have to catch up with these “proprietary” stacks in terms of reliability, security, and performance.

There is Service Orientation – and that’s good. There is appropriate architecture for a problem solution – and that’s good too. These are two things. Combining the two is excellent. But “Service Oriented Architecture” is not an isolated practice. I’ve started to use “SO/A” to make clear that I mean architecture that benefits from service orientation.

I understand that there is an additional architectural tier of “service orientation” that sits at the business/technology boundary. On that meta-level, there could indeed be something like “service oriented architecture” along the lines of the service convergence that Rafal, Pat and myself were discussing on stage at TechEd Europe last year. But when I see or hear SOA discussed, people speak mostly about technology and software architecture. In that context, selling “SOA” as a completely new software architecture school does not (no longer) make sense to me.

Or am I missing something?

Monday, May 02, 2005 6:34:33 AM (Pacific Daylight Time, UTC-07:00)  #    Comments [9] - Trackback
Technology

Ron Jacobs has posted podcasts of two conversations Ron and Arvindra Sehmi and myself had at Microsoft UK two weeks ago, when we coincidentally ran into each other there.

Monday, May 02, 2005 5:59:54 AM (Pacific Daylight Time, UTC-07:00)  #    Comments [1] - Trackback

 Sunday, April 24, 2005

I just looked at my blog and found that I haven’t written anything in more than three weeks and not anything of any substance in more than 6 weeks. I can’t even believe it’s been that long. Time flies by when you’re busy. I still owe a follow up to this here, and will try to get that done in the next two weeks or so.

So what happened in the past 6 weeks? I learned how to stand and “surf” for several seconds at a time on a snowboard in Vail (Colorado) and bruised every part of my body the next week when my friends put me up on a real mountain in Keystone. I had the honor of sitting on the review board of the Microsoft Certified Architect program in Redmond, attended the Indigo Software Design Review in Seattle, spoke at the Visual Studio User Groups in Denver and Boulder (Tim Huckaby gave me 15 minutes of his time at the latter), and had several customer meetings in the US and Germany. I recorded 8 hours worth of webcasts on Service Orientation and spoke at workshops on the same topics in Belgium and Germany.  I spoke at the Microsoft Gulf Developer Conference GDC2005 in Riyadh (Saudi Arabia), at the Microsoft North Africa Developer Conference NDC2005 in Algiers (Algeria), and between all these things I ported an application to Indigo and prepared my talks for several conferences that are happening this next week and later this year and for which the content deadlines were due.

Now, if that sounds busy, consider next week: Today I fly to Istanbul at 17:30h, get there at 21:30h. I will do 3 talks at a large MS conference in Istanbul the next day. Tuesday morning (really: middle of the night) I have to get out to the airport and catch a 5:40am flight to Ljubljana in Slovenia. From there I will be picked up and driven to Opatija in Croatia where I’ll do a track keynote and another talk at the WinDays conference in the afternoon. After dinner, I go back to Ljubljana aiport and fly (at 11:45pm) back to Istanbul, getting there at 2:50am. 3 more talks in Turkey on Wednesday. Then, Thursday morning, I catch the same flight to Ljubljana at 5:40am, but will connect through to Vienna in Austria where I will arrive at around 8:30am and will hurry to the Microsoft office to do two full days of Visual Studio 2005 training for the MS Ascend program and then fly home to Düsseldorf Friday evening. By Saturday I will likely need medical attention.

The upcoming week is so crazy that I will try to document it here. Let’s see whether I can pull it off.

Sunday, April 24, 2005 1:12:40 AM (Pacific Daylight Time, UTC-07:00)  #    Comments [6] - Trackback
Talks
 Friday, April 01, 2005

You can make a difference on this day in April. Go and sign this petition. Help save IDL.

What I personally would love to see are two new switched alongside midl.exe /mktyplib203:  

/wsdl – Verifies that all attributes used are applicable to a wsdl contract and then generates a *.wsdl file from IDL

/serviceModel – Verifies that all attributes are System.ServiceModel compatible and then generates a *.cpp file (managed code!) definition Indigo [ServiceContract] and [OperationContract].

Friday, April 01, 2005 7:54:13 AM (Pacific Daylight Time, UTC-07:00)  #    Comments [2] - Trackback

 Wednesday, March 16, 2005

The Indigo bits are out at MSDN Subscriber downloads. Go get them and start playing.

  Tools, SDKs and DDKs,
      Platform Tools, SDKs, DDKs
          WinFX SDK – Community Technology Preview
              Avalon and Indigo Community Technology Preview - March 05 (English)

Wednesday, March 16, 2005 6:22:59 PM (Pacific Daylight Time, UTC-07:00)  #    Comments [2] - Trackback
Indigo
 Tuesday, March 01, 2005

I’ll write a few more parts of my little Indigo series next weekend (too busy during the week), and will move from “throw arbitrary XML on the wire” to typed messages. However, before I’ll do so, I am curious about your opinion and I am asking you to comment (on the blog-site) on which of the following two declarations you would prefer.

I should probably quickly explain a few things before I let you look at the code snippets: [DataContract] attribute essentially replaces [Serializable] for Indigo and is used to label classes than can be serialized by the System.Runtime.Serialization infrastructure into XML or into a binary representation. So the serialization control through attributes is unified and independent of the actual output flavor you choose at runtime. The [DataMember] attribute labels fields or properties that are part of the data contract and should be (de)serialized. Unlike the current serialization models of Remoting (System.Runtime.Remoting.Formatters) and the XML Serializer (System.Xml.Serialization) where the serializers grab anything public, this model is strictly opt-in, meaning that public fields and properties do not get serialized unless you explicitly label them with [DataMember]. Even more surprising, the new serialization infrastructure does work with fields that are private.

I have a clear preference for one of these two declarations and have also what I think to be a solid explanation for why I prefer it, but before I elaborate, I am interested in your opinion.

Version A

[DataContract]
public partial class Address
{
    [DataMember("Company")]
    private string company;
    [DataMember("RecipientName")]
    private string recipientName;
    [DataMember("AddressLine1")]
    private string addressLine1;

    ... more fields ...

    public string Company
    {
        get { return company; }
        set { company = value; }
    }
   
    public string RecipientName
    {
        get { return recipientName; }
        set { recipientName = value; }
    }
   
    public string AddressLine1
    {
        get { return addressLine1; }
        set { addressLine1 = value; }
    }

    ... more properties and methods and stuff ...
}

 Version B

[DataContract]
public partial class Address
{
    private string company;
    private string recipientName;
    private string addressLine1;

    ... more fields ...

    [DataMember("Company")]
    public string Company
    {
        get { return company; }
        set { company = value; }
    }

    [DataMember("RecipientName")]
    public string RecipientName
    {
        get { return recipientName; }
        set { recipientName = value; }
    }
    [DataMember("AddressLine1")]
    public string AddressLine1
    {
        get { return addressLine1; }
        set { addressLine1 = value; }
    }

    ... more properties and methods and stuff ...
}

Consider this obvious statement: The class is declared in this way to provide programmatic access to and encapsulation of data that will eventually be serialized into some wire format or deserialized from a wire format.

Tuesday, March 01, 2005 6:27:00 AM (Pacific Standard Time, UTC-08:00)  #    Comments [16] - Trackback
Indigo
 Sunday, February 27, 2005

Christian Weyer is staying at my place for the next three nights, because we’re both presenting at a Microsoft Visual Studio 2005 training at Microsoft’s Neuss office, which is more or less down the street (highway) from where I live. Christian brought some good beer from his region (Franken – Bavaria’s northern part) and we’re having some of that, watch some TV (“We Were Soldiers” and “Broken Arrow”, we’re just guys like the next one), and otherwise get some email done, and chat. We just agreed on our programming hero. The prize goes to: Lutz Roeder. We’d be nothing without Reflector.

Sunday, February 27, 2005 2:00:00 PM (Pacific Standard Time, UTC-08:00)  #    Comments [9] - Trackback
Other Stuff
 Saturday, February 26, 2005

[Read Part 1 and Part 2 first]

Like with parts 1 and 2, I’ll stick with the “this isn’t RPC” theme for this 3rd part of this little series and will show how to flow free form XML from and to services. However, I will drop the “client”/”server” nomenclature from here on and will talk about endpoints. If you look at the contract below (along with the following explanation, of course), you’ll quickly figure out why – both parties in the “buyer”/”seller” conversation I am declaring in the contract below, act as client and as server at the same time.

In contrast to the previous two examples, I am not using the raw Message class, but I move one notch up on the messaging stack and use the XmlSerializer formatting mode for Indigo, which allows me to flow the contents of an XmlNode between services just like it can be done today with ASP.NET Web Services. In addition, I show how custom message headers can be declared and flowed with (really: inside) messages. But first things first:

The snippet below declares one contract (!) with two endpoint service contracts. One endpoint defines the “seller” side and the other defines the “buyer” side of a duplex conversation that two service implementations will have about a (simplified) purchasing process. It also defines an application-specific (SOAP-) header that is used to flow the purchasing process identifier between the parties. That identifier can be used to locate the process state from disk or from some in-memory location at either side as the conversation progresses.

The seller-side service contract is defined through the ISeller interface that is appropriately labeled with a [ServiceContract] attribute and the buyer-side likewise defined through the IBuyer interface. The fusion of these two interfaces into what is effectively a single contract is established by mutually linking both interfaces by setting the respective CallbackContract property of the [ServiceContract] attribute to the respective other interface type. I highlighted the two places where that’s being done.

When I say “one contract”, that is not really true on the WSDL level. In WSDL, both interfaces would indeed be represented as independent contracts. (Which goes to show that WSDL isn’t really “the contract”, but represents just a subset of the complete metadata model).

Each operation in these contracts is labeled with an [OperationContract] attribute that defines the message flow as IsOneWay=true. That’s so because in a duplex conversation, messages flow always unidirectionally and the receiver answers not by “returning a result”, but rather by sending a message (or multiple messages) to the other party’s endpoint. All operation contracts also define the operation style to be DocumentBare, which means that the infrastructure will not auto-generate body wrapper elements.

Instead, each operation defines its own body wrapper by flagging the XmlNode typed argument for the message content with a [MessageBody] attribute and assigning an appropriate name to it.  Above the XmlNode content argument, you can see how the custom header PurchaseProcessHeader is specified for each operation. Custom headers are flagged with the [MessageHeader] attribute and therefore flow in the soap:Header section of the message.

using System;
using System.Collections.Generic;
using System.ServiceModel;
using System.Runtime.Serialization;
using System.Xml;
using System.Xml.Serialization;

namespace DuplexMessagingConversation
{
    [XmlRoot(Namespace = PurchaseProcessHeader.NamespaceURI)]
    [XmlType(Namespace = PurchaseProcessHeader.NamespaceURI)]
    public class PurchaseProcessHeader
    {
        public const string NamespaceURI="urn:newtelligence-com:indigosamples:purchasing";
        public const string ElementName="PurchaseOrder";

        private string orderIdentifier;
       
        public string OrderIdentifier
        {
            get { return orderIdentifier; }
            set { orderIdentifier = value; }
        }
    }

    [ServiceContract(Namespace = "urn:newtelligence-com:indigosamples:seller",
                     Session = false,
                     CallbackContract = typeof(IBuyer),
                     FormatMode = ContractFormatMode.XmlSerializer)]
    interface ISeller
    {
        [OperationContract(IsOneWay=true,IsInitiating=true,
                           Style=ServiceOperationStyle.DocumentBare)]
        void HandlePurchaseOrder(
            [MessageHeader(Name=PurchaseProcessHeader.ElementName,
                           Namespace=PurchaseProcessHeader.NamespaceURI)]
            PurchaseProcessHeader process,
            [MessageBody(Name="PurchaseOrderMessage")]
            XmlNode purchaseOrder);

        [OperationContract(IsOneWay = true, IsInitiating = false,
                           Style = ServiceOperationStyle.DocumentBare)]
        void HandlePaymentNotification(
            [MessageHeader(Name = PurchaseProcessHeader.ElementName,
                           Namespace = PurchaseProcessHeader.NamespaceURI)]
               PurchaseProcessHeader process,
            [MessageBody(Name = "PaymentNotificationMessage")]
               XmlNode paymentNotification);

        [OperationContract(IsOneWay = true, IsInitiating = false, IsTerminating = true,
                           Style = ServiceOperationStyle.DocumentBare)]
        void HandleShippingConfirmation(
            [MessageHeader(Name = PurchaseProcessHeader.ElementName,
                           Namespace = PurchaseProcessHeader.NamespaceURI)]
            PurchaseProcessHeader process,
            [MessageBody(Name = "ShippingConfirmationMessage")]
            XmlNode shippingConfirmation);
    }

    [ServiceContract(Namespace="urn:newtelligence-com:indigosamples:buyer",
                     Session = false,
                     CallbackContract = typeof(ISeller),
                     FormatMode=ContractFormatMode.XmlSerializer)]
    interface IBuyer
    {
        [OperationContract(IsOneWay = true, IsInitiating = true,
                           Style = ServiceOperationStyle.DocumentBare)]
        void HandlePurchaseOrderConfirmation(
            [MessageHeader(Name = PurchaseProcessHeader.ElementName,
                           Namespace = PurchaseProcessHeader.NamespaceURI)]
            PurchaseProcessHeader process,
            [MessageBody(Name = "PurchaseOrderConfirmationMessage")]
            XmlNode purchaseOrderConfirmation);

        [OperationContract(IsOneWay = true, IsInitiating = false,
                           Style = ServiceOperationStyle.DocumentBare)]
        void HandleInvoice(
            [MessageHeader(Name = PurchaseProcessHeader.ElementName,
                           Namespace = PurchaseProcessHeader.NamespaceURI)]
            PurchaseProcessHeader process,
            [MessageBody(Name = "InvoiceMessage")]
            XmlNode invoice);

        [OperationContract(IsOneWay = true, IsInitiating = false, IsTerminating = true,
                           Style = ServiceOperationStyle.DocumentBare)]
        void HandleShippingNotification(
            [MessageHeader(Name = PurchaseProcessHeader.ElementName,
                           Namespace = PurchaseProcessHeader.NamespaceURI)]
            PurchaseProcessHeader process,
            [MessageBody(Name = "ShippingNotificationMessage")]
            XmlNode shippingNotification);
    }
}

To illustrate the effect of these declarations on the wire (I will spare you the XSD/WSDL goop), I’ll show an sample message (grabbed from the debugger) as it can be seen at the ISeller endpoint’s HandlePurchaseOrder operation when it arrives.    

<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"
           
xmlns:a="http://schemas.xmlsoap.org/ws/2004/08/addressing"
           
xmlns:r="http://schemas.xmlsoap.org/ws/2005/01/rm">
    <
s:Header>
        <
a:Action s:mustUnderstand="1">
            urn:newtelligence-com:indigosamples:seller/ISeller/HandlePurchaseOrder
        </a:Action>
        <
h:PurchaseOrder xmlns="urn:newtelligence-com:indigosamples:purchasing"
                        
xmlns:h="urn:newtelligence-com:indigosamples:purchasing"
                        
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                        
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
            <
OrderIdentifier>1234567890</OrderIdentifier>
        </
h:PurchaseOrder>

        <
r:Sequence s:mustUnderstand="1">
            <
r:Identifier>uuid:b99041bf-fab8-45dd-9235-0909d9c61d04;id=2</r:Identifier>
            <
r:MessageNumber>1</r:MessageNumber>
        </
r:Sequence>
        <
a:From>
            <
a:Address>net.tcp://localhost/buyer/reply/e01289a8-424f-4e1a-bba5-b3fb7c92a023</a:Address>
        </
a:From>
        <
a:To s:mustUnderstand="1">net.tcp://localhost/seller</a:To>
    </
s:Header>
    <
s:Body>
        <
PurchaseOrderMessage xmlns="urn:newtelligence-com:indigosamples:seller">
            <
Order xmlns="">...</Order>
        </
PurchaseOrderMessage>

    </
s:Body>
</
s:Envelope>

So … having the contract declaration in place, we can build the service. With your knowledge from the previous parts of this series, the seller side is (almost) straightforward to implement. I create a SellerService supporting the defined ISeller interface and write all operations (methods) in a similar fashion. First I dump out the content of the incoming message and an artificial instance identifier I use to play with instancing.  The only “magic” is in how I obtain the callback channel that I need to be able to send my answers to the other side. To be precise, the magic isn’t mine, it’s sitting inside Indigo. The call  IBuyer buyer = OperationContext.Current.GetCallbackChannel<IBuyer>() yields a ready-to-use channel that is properly configured and bound to the “other side”. Having that in hands, I cook up an answer (or two, or none, as you can see below) and send that to “the buyer”. The hosting class and the service host are standard fare.

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

namespace DuplexMessagingConversation
{
    [ServiceBehavior(InstanceMode = InstanceMode.PrivateSession)]
    class SellerService : ISeller
    {
        Guid instanceId = Guid.NewGuid();

        public void HandlePurchaseOrder(PurchaseProcessHeader process, XmlNode data)
        {
            Console.WriteLine("Seller: Purchase Order Received\n\t{0}\n\tInstance {1}",
                              data.OuterXml, instanceId);
            IBuyer buyer = OperationContext.Current.GetCallbackChannel<IBuyer>();

            XmlDocument orderConfirmation = new XmlDocument();
            orderConfirmation.LoadXml("<OrderConfirmation>...</OrderConfirmation>");
            buyer.HandlePurchaseOrderConfirmation(process, orderConfirmation);

            XmlDocument invoice = new XmlDocument();
            invoice.LoadXml("<Invoice>...</Invoice>");
            buyer.HandleInvoice(process, invoice);
        }

        public void HandlePaymentNotification(PurchaseProcessHeader process, XmlNode data)
        {
            Console.WriteLine("Seller: Payment Notification Received\n\t{0}\n\tInstance {1}",
                              data.OuterXml, instanceId);
            IBuyer buyer = OperationContext.Current.GetCallbackChannel<IBuyer>();

            XmlDocument shippingNotification = new XmlDocument();
            shippingNotification.LoadXml("<Shipped>...</Shipped>");
            buyer.HandleShippingNotification(process, shippingNotification);
        }

        public void HandleShippingConfirmation(PurchaseProcessHeader process, XmlNode data)
        {
            Console.WriteLine("Seller: Shipping Confirmation Received\n\t{0}\n\tInstance {1}",
                              data.OuterXml, instanceId);
        }
    }

    class Seller
    {
        ServiceHost<SellerService> serviceHost;

        public void Open()
        {
            serviceHost = new ServiceHost<SellerService>();
            serviceHost.Open();
        }

        public void Close()
        {
            serviceHost.Close();
        }
    }
}

The buyer-side’s service implementation looks almost identical. The one significant difference here is that the buyer is (in the self-hosted scenario I have here: must be) a singleton within the scope of the conversation. That means that the initiator of the conversation (what we usually call “client”) will have to create a service instance and hand that down into the infrastructure. Because I want to know when the conversation is over and can shut down my test program, I hand a ManualResetEvent to the service instance and have it Set it to signaled whenever the buyer’s last expected message in the purchasing process arrives (shipping notification). Otherwise the service implementation doesn’t have any more surprises.

More interesting is the InitiatePurchase method. It predictably creates a service host instance for the buyer service and a channel factory that we need to send the first message (purchase order) to the seller. From there onwards, things are a little different than in the previous examples.

As the next step, I create a “service site”, which acts as the manager for the duplex conversation we’re setting up. The ServiceSite is initialized with the service host and a newly created service instance. As I indicated in the previous paragraph, that instance is a singleton for the conversation; it’s not a singleton per-se.

Using the service site as an argument, I can now create a duplex channel with a call to CreateDuplexChannel on the channel factory. The resulting channel is set up to do everything necessary to listen for answers in the scope of the conversation and to relay the required “send answers here” info to the other side. If you look at the SOAP message above, you’ll see how that back reference is flowing using a WS-Addressing wsa:From header, which is a reasonable thing to do as per WS-Addressing (see: 3. / [reply endpoint] paragraph).

Once I have the channel in hands, I create the custom header instance and a purchase order document (well…) and send it off to the seller side. Once that’s done, I hang out and wait until the conversation is over and subsequently shut down.

Using System;
using System.Xml;
using System.ServiceModel;
using System.Threading;

namespace DuplexMessagingConversation
{
    class BuyerService : IBuyer
    {
        Guid instanceId = Guid.NewGuid();
        ManualResetEvent waitHandle;

        public BuyerService(ManualResetEvent waitHandle)
        {
            this.waitHandle = waitHandle;
        }

        public void HandlePurchaseOrderConfirmation(PurchaseProcessHeader process, XmlNode data)
        {
            Console.WriteLine("Buyer: Purchase Order Confirmation Received\n\t{0}\n\tInstance {1}",
                               data.OuterXml, instanceId);
            return;
        }

        public void HandleInvoice(PurchaseProcessHeader process, XmlNode data)
        {
            Console.WriteLine("Buyer: Invoice Received\n\t{0}\n\tInstance {1}",
                               data.OuterXml, instanceId);
            ISeller seller = OperationContext.Current.GetCallbackChannel<ISeller>();

            XmlDocument paymentNotification = new XmlDocument();
            paymentNotification.LoadXml("<Payment>...</Payment>");
            seller.HandlePaymentNotification(process, paymentNotification);
        }

        public void HandleShippingNotification(PurchaseProcessHeader process, XmlNode data)
        {
            Console.WriteLine("Buyer: Shipping Notification Received\n\t{0}\n\tInstance {1}",
                               data.OuterXml, instanceId);
            ISeller seller = OperationContext.Current.GetCallbackChannel<ISeller>();

            XmlDocument shippingConfirmation = new XmlDocument();
            shippingConfirmation.LoadXml("<ShipmentReceived>...</ShipmentReceived>");
            seller.HandleShippingConfirmation(process, shippingConfirmation);
            waitHandle.Set();
        }
    }

    class Buyer
    {
        public void InitiatePurchase()
        {
            ServiceHost<BuyerService> buyerHost = new ServiceHost<BuyerService>();
            using (ChannelFactory<ISeller> channelFactory = new ChannelFactory<ISeller>("clientChannel"))
            {
                ManualResetEvent conversationDone = new ManualResetEvent(false);
                using (ServiceSite replyTarget = new ServiceSite(buyerHost, new BuyerService(conversationDone)))
                {
                    ISeller channel = channelFactory.CreateDuplexChannel(replyTarget);

                    PurchaseProcessHeader header = new PurchaseProcessHeader();
                    header.OrderIdentifier = "1234567890";

                    XmlDocument purchaseOrderDocument = new XmlDocument();
                    purchaseOrderDocument.LoadXml("<Order>...</Order>");
                    channel.HandlePurchaseOrder(header, purchaseOrderDocument);

                    conversationDone.WaitOne();
                    replyTarget.Close();
                }
                channelFactory.Close();
            }
            buyerHost.Close();
        }
   }
}

The Program is simple and predictable; I am just posting it for completeness and because I renamed the classes.

using System;

namespace DuplexMessagingConversation
{
    class Program
    {
        static void Main(string[] args)
        {
            Seller server = new Seller();
            server.Open();

            Buyer client = new Buyer();
            client.InitiatePurchase();

            Console.WriteLine("Press ENTER to quit");
            Console.ReadLine();
            server.Close();
        }
    }
}

The configuration file that goes with this example is of course a bit different from the previous ones. The <client> section and the buyerClientBinding binding configuration apply to the buyer side, and the <services> section and the sellerBinding are for the seller side. These sections would be respectively split across two configuration files, if we would host the sample in two processes.

Of course, the buyer’s <client>/<endpoint> definition for the channel refers to the buyerClientBinding. That binding defines three required binding elements: <reliableSession> configures the channel to use a reliable messaging session with default values, <compositeDuplex/> enables duplex support and <tcpTransport/> selects the TCP transport. The order of these elements is significant and defines how these “behaviors” are stacked in the channel. Quite special is the clientBaseAddress attribute of the <compositeDuplex/> element; this value is used as the base URI to dynamically construct the endpoint on which replies shall be received by the buyer instance for this conversation. The result of that composition can be seen in the wsa:From element in the SOAP message above.

The seller-side configuration for the <service> and its <endpoint> is largely equivalent to what I’ve explained in the previous examples. The only real difference is that the sellerBinding binding now also defines the required binding elements and behaviors I just pointed out.

<?xml version="1.0" encoding="utf-8" ?>
<
configuration xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">
    <
system.serviceModel>
        <
bindings>
            <
customBinding>
                <
binding configurationName="sellerBinding">
                    <
reliableSession/>
                    <
compositeDuplex/>
                    <
tcpTransport/>
                </
binding>
                <
binding configurationName="buyerClientBinding">
                    <
reliableSession/>
                    <
compositeDuplex clientBaseAddress="net.tcp://localhost/buyer/reply"/>
                    <
tcpTransport/>
                </
binding>
            </
customBinding>
        </
bindings>
        <
client>
            <
endpoint address="net.tcp://localhost/seller"
                      
bindingConfiguration="buyerClientBinding"
                      bindingType="customBinding"
                     
configurationName="clientChannel"
                     
contractType="DuplexMessagingConversation.ISeller, DuplexMessagingConversation"/>
        </
client>
        <
services>
            <
service serviceType="DuplexMessagingConversation.SellerService, DuplexMessagingConversation">
                <
endpoint contractType="DuplexMessagingConversation.ISeller, DuplexMessagingConversation"
                     address="net.tcp://localhost/seller"
                     bindingType="customBinding"
                     bindingConfiguration="sellerBinding" />
            </
service>
        </
services>
    </
system.serviceModel>
</
configuration>

And, lastly, here’s the output:

Seller: Purchase Order Received

        <Order xmlns="">...</Order>

        Instance eb628fce-ac56-43af-9326-5bfc62a101dc

Buyer: Purchase Order Confirmation Received

        <OrderConfirmation xmlns="">...</OrderConfirmation>

        Instance c1ce0c0f-fb98-4432-86fb-c81ac7243295

Buyer: Invoice Received

        <Invoice xmlns="">...</Invoice>

        Instance c1ce0c0f-fb98-4432-86fb-c81ac7243295

Seller: Payment Notification Received

        <Payment xmlns="">...</Payment>

        Instance eb628fce-ac56-43af-9326-5bfc62a101dc

Buyer: Shipping Notification Received

        <Shipped xmlns="">...</Shipped>

        Instance c1ce0c0f-fb98-4432-86fb-c81ac7243295

Seller: Shipping Confirmation Received

        <ShipmentReceived xmlns="">...</ShipmentReceived>

        Instance eb628fce-ac56-43af-9326-5bfc62a101dc

Press ENTER to quit

Again, the messages are free form XML, so I am using Indigo strictly as a raw messaging platform. It’s just a bit more powerful. ;-) If I’d show you a functionally equivalent application based on System.Messaging and MSMQ, you wouldn’t be done reading, yet.  

Saturday, February 26, 2005 12:51:43 PM (Pacific Standard Time, UTC-08:00)  #    Comments [1] - Trackback
Indigo