February 27, 2005
@ 10:00 PM

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.

Categories: Other Stuff

[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.  

Categories: Indigo

[You should read Part 1 of this little series before you proceed reading this one.]

In this 2nd part I am extending the simple messaging example of Part 1 by adding some explicit WS-Addressing trickery. Addressing is so fundamental that its properties are baked right into the Headers collection of the Indigo Message. Even though there are (and I will eventually show) much easier ways to do request/reply management that hide most of what I am doing here very conveniently under the covers, I’ll give you an example of how you can send messages to a service and then instruct the service to explicitly reply back to an endpoint you provide. To make it a little more fun, I am setting up two alternate reply endpoints and have the service come back to them in turns. The Program host class is identical to the one the previous example, so I’ll show only client and service code along with the config.

The server-side code below grew a little bit as you can see. Now, there is a IGenericReplyChannel that is the contract for the replies. It looks suspiciously like the client-side’s IGenericMessageChannel and it is indeed a copy/paste clone of it. I just didn’t want to share code between client and server side. The Receive method has changed insofar as that it no longer prints the message to the console, but now creates a reply and sends the reply to the endpoint that the client indicates through the (WS-Adressing-) ReplyTo header of the incoming message.

To do this, the service constructs a ChannelFactory< IGenericReplyChannel>, using the endpoint address indicated in the incoming message’s ReplyTo header and getting the binding information from the “replyChannel” client setting in the config file shown further down. (Note that this is a bit simplistic, because it assumes that the ReplyTo EPR uses a compatible binding. There is a brilliant way to fix this, but … later). Then, the message body of the incoming message is read into an XmlDocument and if this was a real application, it would likely do something here. For now, we just leave the content as it is and punt it back out.

To construct the reply message, I don’t use the CreateReplyMessage() method provided on the Message class, simply because it doesn’t have an appropriate overload to deal with an XmlReader in the same way as Message.CreateMessage() does. I am sure that’s a minor oversight that’s just a problem with my particular Indigo build. Creating a reply is quite simple, though. All I need to do is to copy the incoming message’s MessageID value into the RelatesTo.Reply property of the outgoing message. For simplicity, I don’t check whether that header is present and set, which I really should do, because there is no actual contract or policy in place (for now). Once I have the reply constructed, just copying the incoming body into it, I send it out through a channel (“proxy”) constructed by channel factory.

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

namespace SimpleAddressing
{
    [ServiceContract]
    interface IGenericMessageEndpoint
    {
        [OperationContract(IsOneWay = true, Action = "*")]
        void Receive(Message msg);
    }

    [ServiceContract]
    interface IGenericReplyChannel
    {
        [OperationContract(IsOneWay = true, Action = "*")]
        void Send(Message msg);
    }

    class GenericMessageEndpoint : IGenericMessageEndpoint
    {
        public void Receive(Message msg)
        {
            using (ChannelFactory<IGenericReplyChannel> channelFactory =
                new ChannelFactory<IGenericReplyChannel>(msg.Headers.ReplyTo, "replyChannel"))
            {
                XmlDocument doc = new XmlDocument();
                doc.Load(msg.GetBodyReader());

                // There is a msg.CreateReplyMessage(...), but that is missing the XmlReader ctor overload
                using (Message reply = Message.CreateMessage("urn:some-action-reply", new XmlNodeReader(doc)))
                {
                    reply.Headers.RelatesTo.Reply = msg.Headers.MessageID;
                    IGenericReplyChannel replyChannel = channelFactory.CreateChannel();
                    replyChannel.Send(reply);
                }
                channelFactory.Close();
            }
        }
    }

    class Server
    {
        ServiceHost<GenericMessageEndpoint> serviceHost;

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

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

Having a reply-enabled server-side, we can now get to the juicy part: the client. Since we now need to listen for replies, the client has to expose a reply-endpoint and therefore also act as a server. (That is the reason why “endpoint” is preferred in service-land rather than the “client”/”server” nomenclature). Therefore, I define a IGenericReplyEndpoint contract (no surprises there) and implement that in GenericReplyEndpoint. To make the example a bit more fun, the constructor of that service class takes two arguments: The client argument refers to an instance of the Client application class and epName gives the service instance (!) a name. The client reference is used to let the client application know how many messages were already received so that it can shut down, once the expected replies for all sent messages have come back. The notification about received messages is done inside the ReceiveReply method,  which otherwise just writes the message body to the console.

Unlike the previous example, this service implementation isn’t used directly. Instead, I derive two subclasses from it: ReplyEndpointA and ReplyEndpointB. These two classes each implement a constructor that passes “A” and “B”, respectively, for the epName argument to the base-class and pass-through the client argument. In case you wonder how the ServiceHost could possibly construct instances of these service classes, not knowing the appropriate parameters to pass to them: Instances of these two classes are pre-constructed and fed into the service host as singletons as you will see below.

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

namespace SimpleAddressing
{
    [ServiceContract]
    interface IGenericMessageChannel
    {
        [OperationContract(IsOneWay = true, Action = "*")]
        void Send(Message msg);
    }

    [ServiceContract]
    interface IGenericReplyEndpoint
    {
        [OperationContract(IsOneWay = true, Action = "*")]
        void ReceiveReply(Message msg);
    }

    class GenericReplyEndpoint : IGenericReplyEndpoint
    {
        Client client;
        string epName;

        public GenericReplyEndpoint(Client client, string epName)
        {
            epName = epName;
            client = client;
        }

        public void ReceiveReply(Message msg)
        {
            XmlDictionaryReader xdr = msg.GetBodyReader();
            Console.Write("{0}: ", epName);
            Console.WriteLine(xdr.ReadOuterXml());
            client.MessageReceived();
        }
    }

    class ReplyEndpointA : GenericReplyEndpoint
    {
        public ReplyEndpointA(Client client):base(client, "A")
        {
        }
    }

    class ReplyEndpointB : GenericReplyEndpoint
    {
        public ReplyEndpointB(Client client)
            : base(client, "B")
        {
        }
    }

    … continued below …

The Client application class is a bit more intricate than the previous version, but there is no rocket science there. I have a counter for the number of messages received and a ManualResetEvent that is getting signaled whenever the number of received messages matches (or exceeds) the number of sent messages. That happens in the MessageReceived method, which is called by the service singletons. The class also has a UniqueIDGenerator, which is an Indigo-supplied class that lets me generate values for the MessageID header that is required alongside using ReplyTo.

In the SendLoop method, I now create two service host instances that shall receive the replies to messages I send; one of type ServiceHost<ReplyEndpointA> and one of type ServiceHost<ReplyEndpointB>. Each of these hosts receives an instance of its service type as a construction argument. Doing so causes the service host to operate in singleton mode, meaning that it will not create new service instances out of and by itself, but rather use only the exact instance supplied here. In the actual send loop, I alternate (i%2==0) between those two service hosts and invoke SendMessage passing the channel factory (not the channel as in the previous example) and the chosen ServiceHost instance.

In SendMessage, I do a few simple things and only one not-so-obvious thing. A new message is constructed as the first step and loaded with an action and the body content. Then I grab the destination address from the channel factory, which sits in the channel factory’s Description.Endpoint.Address property and assign that to the message’s To header. The MessageID is set to a new unique identifier created using the messageIdGenerator. All that is pretty straightforward. Not immediately clear might be what I am doing with the ReplyTo header:

Once a service host is Open, it’s bound to set of endpoints and is actively listening on those endpoints using “endpoint listeners”. I am writing “set of endpoints”, because a service might have several. Each service can expose as many endpoints as it likes; each with a separate binding (transport/behavior/address) and each with a separate contract. There are puzzling special cases, of which you’ll see at least one in this series, where a service listens and properly responds to a contract type that is nowhere to be seen on the actual service implementation. The active endpoints sit on the EndpointListeners collection.

For simplicity (again, this is a bit naïve, but serves the purpose for the time being) and to obtain a ReplyTo address to pass to the service I am sending the message to, I reach into that collection and grab the first available endpoint listener’s address. What I should be doing here is to check whether that listener is indeed the one for the IGenericReplyEndpoint contract and whether I can find one with a binding that is mostly compatible with the one the outbound channel uses. The latter selection would be done to make sure that if I send out via “net.tcp” and I expose a “net.tcp” endpoint myself, I would preferably pass that endpoint instead of a possible “http” endpoint I might be listening on at the same time. Once ReplyTo is set, I send the message out.   

    … continuation from above …

    class Client
    {
        const int numMessages = 15;
        int messagesReceived;
        ManualResetEvent allReceived;
        UniqueIDGenerator messageIdGenerator;
        XmlDocument contentDocument;

        public Client()
        {
            messagesReceived = 0;
            allReceived = new ManualResetEvent(false);
            messageIdGenerator = new UniqueIDGenerator();
            contentDocument = new XmlDocument();
            contentDocument.LoadXml("<rose>is a</rose>");
        }

        void SendMessage(ChannelFactory<IGenericMessageChannel> channelFactory,
                         ServiceHost replyService)
        {
            XmlNodeReader content = new XmlNodeReader( contentDocument.DocumentElement);
            using (Message msg = Message.CreateMessage("urn:some-action", content))
            {
                msg.Headers.To = channelFactory.Description.Endpoint.Address;
                msg.Headers.MessageID = messageIdGenerator.Next();
                msg.Headers.ReplyTo = replyService.EndpointListeners[0].GetEndpointAddress();
                IGenericMessageChannel channel = channelFactory.CreateChannel();
                channel.Send(msg);
            }
        }

        public void SendLoop()
        {
            ServiceHost<ReplyEndpointA> replyServiceA = new ServiceHost<ReplyEndpointA>(new ReplyEndpointA(this));
            replyServiceA.Open();
            ServiceHost<ReplyEndpointB> replyServiceB = new ServiceHost<ReplyEndpointB>(new ReplyEndpointB(this));
            replyServiceB.Open();

            using (ChannelFactory<IGenericMessageChannel> channelFactory =
                        new ChannelFactory<IGenericMessageChannel>("clientChannel"))
            {
                channelFactory.Open();
               
                for (int i = 0; i < numMessages; i++)
                {
                    if (i % 2 == 0)
                    {
                        SendMessage(channelFactory, replyServiceB);
                    }
                    else
                    {
                        SendMessage(channelFactory, replyServiceA);
                    }
                }
                channelFactory.Close();
            }
            allReceived.WaitOne();
            replyServiceA.Close();
            replyServiceB.Close();
        }

        public void MessageReceived()
        {
            if (++ messagesReceived >= numMessages)
            {
                allReceived.Set();
            }
        }
    }
}

What’s left is the matching configuration for this. The mechanics of how the configuration maps to the classes and instances are largely the same as in the simple messaging example. A small difference is that the replyChannel client configuration has no target address attribute, because that one is always supplied via ReplyTo (refer to the GenericMessageEndpoint’s Receive method above to see how that is wired up). Oh, yes, and I switched it all to http transport in case you don’t notice. TCP would work just as well, but I felt like I needed a little change. The assumed assembly name for this sample is “SimpleAddressing”, of course.

<?xml version="1.0" encoding="utf-8" ?>
<
configuration xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">
    <
system.serviceModel>
        <
bindings>
            <
customBinding>
                <
binding configurationName="defaultBinding">
                    <
httpTransport/>
                </
binding>
            </
customBinding>
        </
bindings>
        <
client>
            <
endpoint address="http://localhost/genericep"
               
bindingConfiguration="defaultBinding"
                bindingType="customBinding"
               
configurationName="clientChannel"
               
contractType="SimpleAddressing.IGenericMessageChannel, SimpleAddressing"/>
            <
endpoint
                
bindingConfiguration="defaultBinding"
                bindingType="customBinding"
               
configurationName="replyChannel"
               
contractType="SimpleAddressing.IGenericReplyChannel, SimpleAddressing"/>
        </
client>
        <
services>
            <
service serviceType="SimpleAddressing.GenericMessageEndpoint, SimpleAddressing">
                <
endpoint contractType="SimpleAddressing.IGenericMessageEndpoint, SimpleAddressing"
                                    address="http://localhost/genericep"
                                    bindingType="customBinding"
                                    bindingConfiguration="defaultBinding" />
            </
service>
            <
service serviceType="SimpleAddressing.ReplyEndpointA, SimpleAddressing">
                <
endpoint contractType="SimpleAddressing.IGenericReplyEndpoint, SimpleAddressing"
                                    address="http://localhost/genericreplyA"
                                    bindingType="customBinding"
                                    bindingConfiguration="defaultBinding" />
            </
service>
            <
service serviceType="SimpleAddressing.ReplyEndpointB, SimpleAddressing">
                <
endpoint contractType="SimpleAddressing.IGenericReplyEndpoint, SimpleAddressing"
                                    address="http://localhost/genericreplyB"
                                    bindingType="customBinding"
                                    bindingConfiguration="defaultBinding" />
            </
service>
        </
services>
    </
system.serviceModel>
</
configuration>

The output of the sample is predictable, isn’t it? The replies come back in sequence, alternating between the two reply services “A” and “B”.

B: <rose>is a</rose>
A: <rose>is a</rose>
B: <rose>is a</rose>
A: <rose>is a</rose>
B: <rose>is a</rose>
A: <rose>is a</rose>
B: <rose>is a</rose>
A: <rose>is a</rose>
B: <rose>is a</rose>
A: <rose>is a</rose>
B: <rose>is a</rose>
A: <rose>is a</rose>
B: <rose>is a</rose>
A: <rose>is a</rose>
B: <rose>is a</rose>
Press ENTER to quit

 

Categories: Indigo

This weekend I will create some samples for myself as a foundation for learning and fiddling around with Indigo bindings. A “binding” is a combination of transport and behavior settings that binds a service contract to an endpoint and it is a conceptual and functional superset of what wsdl:binding does. One of the great things about Indigo is that changing bindings and therefore adding/removing capabilities and even adding/exchanging/removing transports can be done with no impact on the code itself. All of that can be done in configuration. So what I will do is to share the base samples with you as I write them, explain a couple of concepts along the way, and use some very simplistic bindings for starters. Once I have figured out how the bindings stuff works (Citing one of the Indigettes at Microsoft: “That’s the part of the product that I’m afraid will be rocket science”), I can later reference these samples and show using configuration snippets what behaviors (e.g. transactions, security, reliable messaging) can be used in combination with which transports and contract types. So, watch this space, you can expect some code here.

What I’ll start with is the most simplistic and “raw” way to use Indigo that’s practical for someone with a life. The extensibility model will let you reach even deeper down into the guts, but I don’t want to drag you down there too far. Also, I don’t really know all of the scary dragons lurking there is the dark. I also want to start with this example to dispel some people’s impression that Indigo is just another “square brackets RPC-ish thing”.

The snippet below is likely the simplest possible Indigo service.  I define an IGenericMessageEndpoint contract that has a single operation Receive, which expects an System.ServiceModel.Message as input. The Message class is the immediate representation of a message that the entire Indigo infrastructure uses internally and that can be surfaced to the application using a contract definition like this. The [OperationContract] attribute signals that the operation is “one way”, so it’s clear that we’re not sending any immediate responses and not even faults. The Action is set to “*”, which is a wildcard indicator specifying that all messages, irrespective of their Action URI will de dispatched here. That is, unless there would be another operation without a wildcard Action. In that case, all messages matching the concrete Action URI of that operation would be dispatched there and all other messages would flow into the wildcard operation.

The implementation of the contract in GenericMessageEndpoint just dumps the content of the message body onto the console by acquiring the XmlDictionaryReader of the message and writing the string’ized body content out.

The Server class constructs a ServiceHost<GenericMessageEndpoint> for the service implementation, which constructs endpoints from configuration settings, hosts these endpoints, and is responsible for creating instances of the service as messages arrive and need to be dispatched. As you can see, I do nothing more than constructing the host and Open it. The specifics of what transport is used and where the service is listening will be supplied in config, as you’ll see further down.

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

namespace SimpleMessaging
{
    [ServiceContract]
    interface IGenericMessageEndpoint
    {
        [OperationContract(IsOneWay = true, Action = "*")]
        void Receive(Message msg);
    }

    class GenericMessageEndpoint : IGenericMessageEndpoint
    {
        public void Receive(Message msg)
        {
            XmlDictionaryReader xdr = msg.GetBodyReader();
            Console.WriteLine(xdr.ReadOuterXml());
        }
    }

    class Server
    {
        ServiceHost<GenericMessageEndpoint> serviceHost;

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

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

Below is the matching “raw” client. What we want to do here is to just construct a System.ServiceModel.Message, put some XML into it and throw it over the fence. To do that, I construct a client side contract IGenericMessageChannel (I am doing that to show that we’re really in “contract-free” raw messaging territory here) that has a Send operation, which “looks right” on the sender side vs. the receiver contract’s Receive, and also flags the operation as one-way and with a wildcard Action.

To setup a channel to the destination service, I can now (in SendLoop) construct a ChannelFactory<IGenericMessageChannel> over that contract and with the argument “clientChannel”, which is a reference into the configuration as I’ll show in a little bit. The channel factory is the client-side counterpart of the service host. It reads all information about the channel from the configuration, evaluates the bindings, binds to the right transports and behaviors, and also knows about the endpoint to talk to. Once I have a channel factory, I can Open it and have it give me a channel (or “proxy”) that I can talk through. In SendMessage I cook up a Message from an Action URI that I make up and an XmlReader instance layered over an XmlDocument that I keep around and send that out to the service.

using System;
using System.Xml;
using System.ServiceModel;

namespace SimpleMessaging
{
    [ServiceContract]
    interface IGenericMessageChannel
    {
        [OperationContract(IsOneWay = true, Action = "*")]
        void Send(Message msg);
    }

    class Client
    {
        XmlDocument contentDocument;

        public Client()
        {
            contentDocument = new XmlDocument();
            contentDocument.LoadXml("<rose>is a</rose>");
        }

        void SendMessage(IGenericMessageChannel channel)
        {
            XmlNodeReader content = new XmlNodeReader( contentDocument.DocumentElement);
            using (Message msg = Message.CreateMessage("urn:some-action", content))
            {
                channel.Send(msg);
            }
        }

        public void SendLoop()
        {
            using (ChannelFactory<IGenericMessageChannel> channelFactory =
                        new ChannelFactory<IGenericMessageChannel>("clientChannel"))
            {
                channelFactory.Open();
                IGenericMessageChannel channel = channelFactory.CreateChannel();
                for (int i = 0; i < 15; i++)
                {
                    SendMessage(channel);
                }
                channelFactory.Close();
            }
        }
    }
}

The surrounding application for Client and Server (I run both in the same process for simplicity) is, of course, trivial. All I do is to construct and start the server, construct a client and call its send loop and then wait for the user to be amazed of the (server’s) console output and have him/her press ENTER to quit. If I were making this more elaborate, I could wait until all sent messages had arrived at the service side and shut down automatically, but this is supposed to be simple.

using System;

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

            Client client = new Client();
            client.SendLoop();

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

That’s as much code as we need to implement a one-way messaging client/server “system” that can throw XML snippets across a network transport.

To make it work, we need to configure this application and “deploy” it to a concrete environment. A simple configuration (assuming this is all compiled into “SimpleMessaging.exe” and hence the assembly name is “SimpleMessaging”) could look like the one shown below.

The <bindings> section contains one <customBinding> (means: I am not anything predefined), with a concrete configuration named “defaultBinding” that uses the tcpTransport. If I were setting up security or reliable messaging, would also be doing that here and add the respective config elements alongside the TCP transport binding element, but we will keep it simple for the time being.

The <client> section defines, for the configurationName=”clientChannel” (look above in the client snippet how that maps to the ChannelFactory< IGenericMessageChannel > constructor call), which binding should be used. The example links up to the customBinding type and within that type to the “defaultBinding” config. Furthermore, the section defines to which contract type ([ServiceContract]-labeled interface or class) the endpoint is bound and, lastly and most importantly, the address at which the endpoint is listening to client messages.

The <service> section defines the server side of the story. The association between the service host and the configuration is done via the serviceType attribute. When the ServiceHost<GenericMessageEndpoint> is contructed in the server snippet above, the service host locates the section for the respective service type it is hosting by looking at this attribute. The endpoint definition on the server side is very similar to the client side, which should not be very surprising. It also refers to a binding using bindingType/bindingConfiguration, defines the address at which the service will be listening, and indicates which contract type applies for the endpoint.

<?xml version="1.0" encoding="utf-8" ?>
<configuration xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">
    <system.serviceModel>
        <bindings>
            <customBinding>
                <binding configurationName="defaultBinding">
                    <tcpTransport/>
                </binding>
            </customBinding>
        </bindings>
        <client>
            <endpoint address="net.tcp://localhost/genericep"
                        bindingConfiguration="defaultBinding"
                bindingType="customBinding"
                configurationName="clientChannel"
                        contractType="SimpleMessaging.IGenericMessageChannel, SimpleMessaging"/>
        </client>
        <services>
            <service serviceType="SimpleMessaging.GenericMessageEndpoint, SimpleMessaging">
                <endpoint contractType="SimpleMessaging.IGenericMessageEndpoint, SimpleMessaging"
                                    address="net.tcp://localhost/genericep"
                                    bindingType="customBinding"
                                    bindingConfiguration="defaultBinding" />
            </service>
        </services>
    </system.serviceModel>
</configuration>

Running it all yields the following output, spit out by the server side, and just as expected:

<rose>is a</rose>
<rose>is a</rose>
<rose>is a</rose>
<rose>is a</rose>
<rose>is a</rose>
<rose>is a</rose>
<rose>is a</rose>
<rose>is a</rose>
<rose>is a</rose>
<rose>is a</rose>
<rose>is a</rose>
<rose>is a</rose>
<rose>is a</rose>
<rose>is a</rose>
<rose>is a</rose>
Press ENTER to quit

Very simple and versatile one-way messaging flowing free-form XML. Not at all RPC-ish.

 

Categories: Indigo

February 15, 2005
@ 08:44 PM

The scariest search for which my blog is on Google rank #1 is power=work/time. I knew that Google loves me, but this starts to become pretty ridiculous ;-)

Categories: Blog

February 15, 2005
@ 07:12 PM

CNet reports about Bill Gates’ announcement that Windows Anti-Spyware is going to be free includes the following truly puzzling quote from the Check Point Software CTO:

"I am glad to see Gates is focusing on securing the desktop," said Gregor Freund, chief technology officer of Check Point Software, which develops desktop security software. "However, there are some serious downsides to Microsoft's approach. Just by entering the security market, Microsoft could stall innovation by freezing any kind of spending of venture capital on Windows security which in the long run, will lead to less security, not more."

Is it just me or do you also consider the term “venture capital” as being a little out of place in this context?

Categories: IT Strategy | Other Stuff

February 12, 2005
@ 07:57 AM

Jim Johnson, who works on transaction technology at Microsoft’s Distributed Systems Group (aka „Indigo team“), shares an important insight on ACID’s “D” and that “Durable” doesn’t always mean “to disk”, but is rather relative to the resource lifetime. A transaction outcome can be “durably” stored in memory, if that’s what the characteristic of the resource underlying the resource manager (=transaction participant) is. That’s a very important perspective to have. Once you get away from the “transactions are heavy duty” thinking (because “D” seems to imply “disk” to many people), transactions, especially with “lightweight transaction managers” such as the one found in the .NET Framework 2.0’s System.Transactions.Ltm namespace (or the one I published a while back), suddenly become very attractive to coordinate and resolve fault conditions between components within a process.

Categories: Transactions

February 11, 2005
@ 06:48 AM

Aaron Skonnard says I am clearly wrong with my demand that one shouldn’t have to look at WSDL/XSD/Policy. Well, at this point in time the tooling makes it indeed difficult to ignore angle brackets. But that’s not a reason to give up. I also find the “it all has to start at the angle bracket” stance overly idealistic.

I can type up an XML Schema in notepad, I can even type up a WSDL in notepad. As much as one would like to have it different, both “skills” are not so common amongst the developer population. I would think that for the majority of ASP.NET Web Services in production today, their developers completely ignored the XSD/WSDL details. But even if that were different: The rubber hits the road when we talk about policy. Can you type up a complete and consistent set of policy assertions for integrity and confidentiality and authentication using Kerberos and vX509 tokens without looking at the spec or a cheat sheet? How about combining that with assertions for WS-AT and WS-RM? As long as we keep the story reduced to XSD and WSDL, dealing with angle brackets might something that someone could reasonably expect from a mortal programmer who has a life. One we take policy into the picture, we better start asking for tools that hide all those details. The interoperability problems of getting secure, reliable and transacted web service work together are far harder than just getting services to talk. That’s part of the contract story, too. Yet, I cannot imagine that anybody would seriously demand that we all sit down and explicitly write these endless sequences of policy assertions and then feed our tools with them. At least I don’t want to do that, but that may just be me getting too old for this stuff.

Categories: Indigo

February 10, 2005
@ 10:55 PM

Bruce Williams illustrates how to turn my very simple “Hello World” Indigo sample into a queued service by changing the transport binding from HTTP to MSMQ (I think that’s radically cool). Now, the next step is to illustrate a Duplex conversation to get the response back to the caller. If Bruce or someone else isn’t going to beat me to it, I’ll show that once I get home from Warsaw tomorrow night. [Ah, by the way: Bruce! No need to “Mr.” me ;-)]

Categories: Indigo

February 10, 2005
@ 06:45 AM

Tim Ewald responds (along with a few others) to my previous post about WSDL and states: ”Remember that WSDL/XSD/Policy is the contract, period. Any other view of your contract is just an illusion.

WSDL and XSD and Policy are interoperable metadata exchange formats. That’s just about it. The metadata that’s contained in artifacts compliant with these standards can be expressed in a multitude of different ways. I do care about “my tool” (whatever that is) to do the right thing mapping from and to these metadata standards whenever required and I do care about “my tool” guiding me to stay within the limits of what these metadata formats can express.

But WSDL/XSD/Policy isn’t the contract. If you do ASMX, you can create server and client without you or any of the tools ever looking at or generating WSDL. And it works. If you use Indigo, you can do the same and, in fact, for generating any XML-based metadata from within an Indigo service, it’s even required to explicitly add the respective service behavior at present. The required metadata to make services work comes in many shapes or forms and is, for a given tool, typically richer than what you will find in the related WSDL/XSD/Policy, because not all that metadata is related to the wire format itself.

If I need to tell someone who is not using my tool of choice how to talk to my service, I have my tool generate the respective metadata exchange documents and I want to be able to trust my tool that they’re “right”.

What I am stating here is simply my demand and expectation for the degree of “automatic interoperability” that I expect from the tools. I can read WSDL/XSD/Policy; out there, most people absolutely don’t seem to care about these details and I tend to agree with them that making this stuff work is someone else’s problem.

I don’t need to be able to read and write PDF to use PDF. I use PDF if I know that someone will open my document who is not using Microsoft Word. Still, that PDF doc isn’t the document. My Word source document is the document I edit and revise. The PDF is just one of several possible representations of its contents.

Categories: Indigo

February 9, 2005
@ 06:48 AM

I wish I was at VSLive! in San Francisco to hang out with all of my friends. Instead (and that isn’t too bad, either), I am sitting in my hotel room at the Warsaw Marriott watching the sun rise over the Polish capital. Today and the next two days, my partner Achim Oellers and myself will be teaching a class on service orientation principles, explaining fundamental ideas, patterns, techniques and will go through a lot of concrete implementation guidance for today’s Microsoft MSMQ/WSE/ASMX/ES stack so that our customers can start writing services today. The fundamental principles about data contracts, message contracts and service contracts that we teach will carry forward to Indigo – along with a lot of the implementation techniques (and the resulting source code) that we will suggest. Of course, that has been a bit of a hidden agenda in past workshops, because I couldn’t openly speak about anything that happened to Indigo past PDC03, but now that the Indigo day at VSLive! is over, I can. That makes it even more fun.

Categories: Talks

XML is ugly and angle brackets are for plumbers. Unless you have a good reason to do so, you shouldn’t have to look at WSDL. Sharing this C# snippet here

[ServiceContract]
interface IHello
{
      [OperationContract]
      string SayHello(string name);
}

is a perfectly reasonable way to share contract between server and client, if you’ll be sticking to Indigo. A service can expose all the WS-MetadataExchange and XSD and WSDL you like so that other Web Service clients can bind to your service, but as long as you stay on the System.ServiceModel level and focus on writing a distributed systems solution instead of writing something that “does XML”, you won’t have to worry about all the goo that goes on in the basement. Staring at WSDL is about as interesting as looking at the output of “midl /Oicf”.

Categories: Indigo

February 9, 2005
@ 05:56 AM

using System;
using System.ServiceModel;

namespace IndiHello
{
      [ServiceContract]
      public class Hello
      {
            [OperationContract]
            public string SayHello(string name)
            {
                  return "Hello " + name;
            }
      }

      class Program
      {
            static void Main(string[] args)
            {
                  ServiceHost<Hello> host = new ServiceHost<Hello>(new Uri("http://localhost/hello"));
                  host.AddEndpoint(typeof(Hello), new BasicProfileHttpBinding(), "ep");
                  host.Open();
                  Console.WriteLine("Press ENTER to quit");
                  Console.ReadLine();
                  host.Close();
            }
      }
}

I am told that I can talk, so I do ;-)  Here’s a simple Indigo server. If you looked at the PDC 2003 Indigo bits, you will notice that the programming model changed quite a bit. I think that in fact, every single element of the programming model changed since then. And all for the better. The programming model is so intuitive by now that I am (almost) tempted to say “Alright, understood, next technology, please”.

So up there you have a class with an implicit service contract. An explicit service contract would be a standalone interface (that’s the proper way to do it, but I wanted to keep the first sample simple) with a [ServiceContract] attribute. Here, [ServiceContract] sits right on the class. Note that the class doesn’t derive from any special base class. Each method that you want to expose as an endpoint operation is labeled with [OperationContract]. These and a set of other attributes (along with a bunch of options you could set, but which I am not doing for the moment) control how the class contract is exposed to the outside world via Indigo.

In the Main method, you have a ServiceHost, which hosts the service (the class is parameterized with the implementation type) and which is initialized with the base-adress at which the service shall be hosted. The base address here is “http://localhost/hello” and with that maps into the namespace of http.sys at port 80. The endpoint can exist alongside any IIS-hosted websites, even though this particular app is hosted in its own little console-based app.

Into this host, I map the service contract with a BasicProfileHttpBinding() to the endpoint address “ep”, which means that messages to that particular service that flow through HTTP using the WS-I Basic Profile 1.0 shall be directed to the “http://localhost/hello/ep” endpoint. Once I have a binding in place (that could also be done in config), I Open() the service and the service listens. Once I am done listening, I Close() the service.

Isn’t too hard.

Categories: Indigo