Following up on
this previous
post about SQL Server 2005 Service Broker, I've been writing a SOAP message
transport for the Web Services Enhancements (WSE) 2.0 to move messages using
ServiceBroker whilst still gaining advantage of the security, policy, addressing
features of WSE2.0.
It's important to say that this is purely for fun because it involves
installing WSE2.0 on top of the beta .NET Framework 2.0 and there is no way in
which the WSE team or the .NET framework team are going to support that kind of
scenario.
The solution files and source code are all in this zip file here which you
can download and play with.
I started with the December Community Tech Preview of SQL Server 2005 and the
accompanying Visual Studio 2005 build - these are available from
MSDN Subscriber Downloads for
you to get hold of.
Following up on my
previous post
I've already created constructs in SQL for SoapMessage, SoapContract and
also that post details creating some services and queues: SendingService,
SendingQueue and ReceivingService, ReceivingQueue. The services and
queues aren't set in stone here but the SoapMessage/SoapContract stuff is baked
into my code.
The implementation is not perfect by any means and I'm sure that there are bugs in there
but it seems to work reasonably well in terms of both synchronously and
asynchronously sending/receiving on both one-way and two-way conversations.
In terms of the structure of it, there are a number of projects as below;
AsyncUtilities - a simple library with a couple of helper classes
that try and make the wrapping up of existing asynchronous code (such as
SqlCommand.BeginExecuteReader) easier to deal with.
BrokerUtilities - this provides a simple library that lets me send
and receive messages through the ServiceBroker. It's just using dynamic SQL
and ADO.NET 2.0
BrokerTransport - this implements the WSE2.0 transport itself,
making use of the BrokerUtilities library to send messages backwards
and forwards.
SQL Service Broker Scripts - this contains a single script to
create my SoapMessage and SoapContract definitions and a couple of test
services.
Test Applications - contains 3 test applications. The first 2 are
simple console applications that send/receive messages through Service
Broker. The third is a Windows application that needs to be run twice and
then has a "conversation with itself" :-)
By way of some explanation (as there are no comments in the code)...
AsyncUtilities
This project contains a class named AsyncResultWrapper - I
blogged about
something very similar to this the other day - it's just a little class
to help make the wrapping up of another class which does asynchronous
operations a little easier.
The other class here is CompletedAsyncResult which is a trick
implementation of IAsyncResult which always will return "Done"!
immediately. There's a place where I need this later on.
BrokerUtilities
There's a class called SqlUtility here which makes open
SqlConnections for me and transactional SqlCommand instances
which are expecting CommandText rather than stored procedures etc. Note that
the view here is that given a database server and a database name we can
generate a connection string and connect to the database (i.e. we just
assume that we can use integrated security) - if that wasn't the case then
you'd need to tweak this class and possibly also the soap.broker Uri
that I'll talk about later.
The other two classes are MessageReceiver and MessageSender
which are static classes offering methods for sending XML through
ServiceBroker. Essentially the methods look like;
void MessageSender.SendMessage( DatabaseServer, DatabaseName,
DestinationService, SendingService, XmlMessage )
List<XmlReader> MessageReceiver.ReceiveMessage( DatabaseServer,
DatabaseName, ReceivingQueue )
There are corresponding asynchronous versions of these methods as well.
Internally they're very simple in that each uses dynamic SQL to send/receive
messages and also there's an assumption in the sending of messages that each
single message will be sent in its own unique dialog and that it's ok for
the receiver to receive everything off its queue without checking
conversations. What I'm really saying is that I don't do conversations
here!
Note also that when ServiceBroker is sending for you it needs to know
about the SendingService and the ReceivingService but when it's receiving it
needs to know about the ReceivingQueue. That's how the programming model
works.
BrokerTransport
This implements the WSE 2.0 transport on top of the BrokerUtilities
library. Implementing a WSE 2.0 transport isn't too bad (at least at the
level that I've done my implementation here which is less than bulletproof).
The first thing you've got to do is go ahead and implement
ISoapTransport and you'll spot that in my BrokerTransport.cs
file. This interface is really about returning implementations of
ISoapInputChannel and ISoapOutputChannel based upon a transport
address that you're given by the WSE. Note that I make a fairly simple
assumption about the mapping between EndpointReference and
input/output channels which is probably not quite right in all scenarios.
In terms of the input and output channels - they're in
BrokerInputChannel.cs and BrokerOutputChannel.cs - these make use
of the underlying BrokerUtilities code to send/receive messages
synchronously or asynchronously.
The other interesting thing here is the BrokerUri class which is,
again, a very simple thing. In terms of addressing a SOAP message to travel
through ServiceBroker I've said that we'll use a Uri that looks like this;
soap.broker:<database server name>-<database
name>-service:<service name>
soap.broker:<database server name>-<database name>-queue:<queue
name>
This needs a bit of explanation. When you're sending a SOAP
message you need to stick an address on it to say where it's going to. This
address will look like
soap.broker:myServer-myDatabase-service:DestinationService and that's
enough to get us where we want to go. However, ServiceBroker also wants to
know where did it come from? in order to begin a conversation.
Consequently, when sending, you still have to supply a ReplyTo
address on your SOAP message with something like
soap.broker:myServer-myDatabase-service:OriginatingService.
So, that's possibly OK and that's what my code expects. However, when
you're receiving messages with ServiceBroker you actually pull them
from a queue rather than receiving them "for a service". Consequently, if
you want to listen for SOAP messages with this transport you need to use a
Uri like soap.broker:myServer-myDatabase-queue:MyQueue.
If you run the sample applications you'll see how they're set up.
TestApplications
The main test application here is just called TestApplication. It
uses the "lower level" SoapSender and SoapReceiver model of
programming in WSE.
The idea is that you run two of these applications and they "talk" to
each other.
You set up each instance as a "sender" by specifing its service name and
the service name it sends to. You also need to have it listen as well so you
specify the queue that it reads from.
Once you've got two of them up and running you click "Ready to Listen" on
both of them and then on one of them you click "Send First Message". That
instance will then generate a shape, draw it and send it to the other
instance. That second instance will then draw it, wait a little while before
generating its "reply shape" and sending it back. They then just go back and
forward sending shapes to each other.
If you use my SQL creation script to generate Services/Queues then you'll
have the same names as me and that will let you use the "Send Defaults" and
"Receive Defaults" buttons to pre-populate the form with the right values
based on that script.
The second set of applications here are called Sender and
Receiver and they're console apps which speak to each other. The names
of the queues and services are baked into the code here so if you use this
you'll need to have the same queues/services that I have.
The idea here is that you run both Sender and Receiver.
Every time you hit return on the Sender it will send a message to the
Receiver which will then send a reply. Both sides of the conversation just
write their messages to the console when they receive them. Again, this is
using the "low level" SoapSender/SoapReceiver API rather than the higher
level SoapService/SoapClient.
SQL Service Broker Scripts
This is a single SQL script which sets up the required SoapContract,
SoapMessage and, if you want to, also creates some services and queues
which the demos make use of.
Configuration
If you want to make use of the transport it's important to remember that
WSE 2.0 doesn't just know about it but has to be told via way of a
configuration file change. The configuration file should look something like
this (as a miniumum);
<?xml
version="1.0"
encoding="utf-8"
?>
<configuration>
<configSections>
<section
name="microsoft.web.services2"
type="Microsoft.Web.Services2.Configuration.WebServicesConfiguration,
Microsoft.Web.Services2, Version=2.0.0.0, Culture=neutral,
PublicKeyToken=31bf3856ad364e35"
/>
</configSections>
<microsoft.web.services2>
<messaging>
<transports>
<add
scheme="soap.broker"
type="BrokerTransport.BrokerTransport,
BrokerTransport"/>
</transports>
</messaging>
</microsoft.web.services2>
</configuration>
That's pretty much it - I'm not planning to do any more with this
transport unless someone comes back and has something interesting to do with it
or would like to change it. If someone does take it and improves it or fixes it
then feel free to drop a comment here saying that you've done so as it'd be
interesting to see.
Posted
Fri, Feb 25 2005 2:05 AM
by
mtaulty