[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
|
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.
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 
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?
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.
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.
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 ]
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.
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.
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”.
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.
I have been invited to speak at the Denver Visual Studio Usergroup on Monday, March 28th. Because I just happen to be in Denver I am delighted to volunteer and talk about the principles of Service Orientation and how to make it happen for real now (ES, ASMX) and tomorrow (Indigo). Mind that this is after VSLive! and I'll be able to tell things I've been told not to tell.
There you go:
Happy Stewardesses who like my Alienware notebook (seems to
work just as well as driving a Lamborghini)
 
And ... chatting with Hanselman and having (economy class
... so much for Lamborghini) food

I am aboard SK938 (SAS) right now. I am on the Internet. Connexion by Boeing. Chatting with Scott Hanselman using MSN Messenger. Blogging this. If there is something like "geek orgasm", this is it. Eight hrs to go to Copenhagen. This R-O-C-K-S.
|