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.
About the author
Clemens Vasters
Blog: http://staff.newtelligence.net/clemensv/default.aspx
Clemens, co-founder of newtelligence, a consulting and developer education company, has more than 14 years of experience as a Developer and Software Architect of financial solutions for the banking industry, portal solutions and application infrastructure services. In 2004 alone, he spoke at conferences and seminars in 29 countries throughout Europe, the Middle East, Africa and in the U.S., including Microsoft TechEd Europe and USA. Clemens is the author of several books, and has architected and implemented multiple reference applications for Microsoft. He is a Microsoft Regional Director and a Solution Architect MVP. Clemens' current work focuses on prescriptive guidance and education around Indigo, service orientation, XML Web Services infrastructures and team collaboration tools.
|