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.