玄铁剑

成功的途径:抄,创造,研究,发明...
posts - 128, comments - 42, trackbacks - 0, articles - 174

Communication options with WCF - Part 3

Posted on 2007-01-03 18:01 玄铁剑 阅读(330) 评论(0)  编辑 收藏 引用 所属分类: Service

Introduction to Part 3

In Part 1 we defined what a service was and also created a simple WCF service and client to demonstrate the process of creating and consuming services under WCF. Part 2 continued with some examples of the various communication patterns that can be easily implemented with WCF. In this article we'll continue by completing the examination of asynchronous communication and then examine the fourth transport supported under WCF, message queue. Note that all the source code for the examples presented are available in the download for Part 1.

July CTP Update

I recently had the opportunity to install the July CTP. I compiled the sample source to see if there were any surprises. It seems that svcutil has had a few changes. And there were some naming changes that will generate some syntax errors if you compile the sample source as is. You should be able to easily identify and fix them, IntelliSense works so much better these days.

Asynchronicity Part du

So, in the last article we developed an example of asynchronous communication based on the .Net Asynch pattern. That functionality was provided entirely on the client and mostly implemented by the .Net infrastructure. Now we'll provide the same functionality (asynchronous communication) but this time we’ll use a Duplex Communication pattern. In this case both the client and the service share in the work. The mechanics are essentially the same as we saw for the Publish/Subscribe except that here there is a 1:1 correlation between the request and the response. Whereas for the Publish/Subscribe pattern, one request (subscription) resulted in many responses (publications). And for Publish/Subscribe there can be any number of clients subscribing to one publication. But in both cases the request is disconnected from the response. Just to be clear, the request is a message and the response is a separate message. Each one though follows a request/reply pattern based on the IsOneWay property setting. Does that make it less clear? Well, I better leave it at that.

For Duplex Communication the client makes a request of the service. The service will then, at some future time send a message back to the client with the results. The service also specifies what the client needs to ‘implement’ by providing a callback interface definition. And in processing the response, the  client behaves almost like a service. Let’s begin by defining a new version of the AGVController (call it AGVControllerD) that supports duplex communication so we can examine each of those points. Here is the revised service code.

[ServiceContract(Session = true, CallbackContract = typeof(IMoveToCallback))]
public interface IAGVControl
{
    ...
}
//What the client needs to implement
public interface IMoveToCallback
{
    [OperationContract(IsOneWay = true)]
    void MoveDone(int location);
}

[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)]
public class AGVControl : IAGVControl
{
    private IMoveToCallback callback = null;
    public AGVControl()
    {
        callback = OperationContext.Current.GetCallbackChannel();
    }
    ...
    public void MoveTo(int location)
    {
        //This takes a long time...
        Thread.Sleep(5000);
        //Send a response
        callback.MoveDone(location);
    }
    ...
}

First thing you’ll notice is that the ServiceContract now specifies a callback interface. The service needs this in order to know what to call. Next is the actual definition of the callback interface that the client has to implement. Since we already know what to call we just need to know who to call. And that is the purpose of the statement in the constructor. Each instance of the service object (we've specified an instancing of per session so ServiceHost will create an instance of the service class for each client session) remembers whom it is communicating with. This is important because the client assumes that the service is maintaining some state (a vehicle) in between method calls. Then when the appropriate time comes the service can make a call back to the client.

One thing to note is that the callback interface is not limited to a single method. We have the same flexibility here as we had with the asynchronous client when we edited the proxy class. There we just implemented a Begin/End pair for one method. In this example we could define any or all of the service methods to also have a callback methods. To compare the two asynchronous examples in the same light, we are only defining a callback for the MoveTo operation. But we could certainly have defined a callback method for each of the service methods.

Compile the service dll and then run the svcutil against it to generate the proxy class. This time we don’t specify the ‘/a’ option. The host is the same as before. All that you’ll have to do is change references to AGVController with AGVControllerD (likewise the namespace in the config file).

Now let’s build the Duplex client. The main difference here is that we need to provide a class that implements the callback interface so that the service can talk back to us. The svcutil automatically includes the definition of the callback interface as part of the proxy. Just as we saw in the Publish/Subscribe example we need some kind of hosting on the client side in order for us to be able to receive the messages from the service. And that functionality is provided by the InstanceContext class just as before. Here is the code specific to the duplex client, everything else is the same as was for the asynchronous client.

public partial class Form1 : Form
{
    AGVControlProxy proxy = null;
    InstanceContext siteAGV = null;
    bool gotVehicle = false;
    public bool gotResponse = false;
    ...
    private void Form1_Load(object sender, EventArgs e)
    {
        siteAGV = new InstanceContext(new MoveCallbackHandler(this));
        proxy = new AGVControlProxy(siteAGV);
    }
    ...
}
public class MoveCallbackHandler : IAGVControlCallback
{
    Form1 parent;
    public MoveCallbackHandler(Form1 parent)
    {
        this.parent = parent;
    }
    public void MoveDone(int location)
    {
        parent.gotResponse = true;
    }
}

If you run this version of the client (make sure the service is running) you should see the same results for both implementations. So if the service provides for asynchronous communication great, but even if it doesn't you can still implement it on the client with just a slight amount of code and revising the proxy accordingly. One more thing, if the requirements are right you could provide both forms of communication on the service and let the client choose.

Queuing it

The fourth communication transport mechanism supported by WCF is the message queue (to be fair, some of the literature describes peer-to-peer communication as another transport type). Message queues are not something new to WCF. It has been a part of Windows but was not available on all versions, which I thought was a bad thing. I think the message queue is an excellent communication mechanism for the right applications. When would you use a message queue? When you need it! A typical situation is for applications that may not have access to the service at all times. They may at times be disconnected.

Another application might be a producer/consumer pattern where each may have different processing speeds. That is, suppose there are a number of data producers and each produces data in irregular cycles. It is possible for all of them to happen to send their data at the same time, which could overwhelm the consumer. So by using a message queue we are essentially spreading out the processing of the data over time. A typical example of this might be a voting application. The vote data might come in spurts because you can’t control when people will cast their vote. In other words the producers can do whatever/whenever they want, the consumer will never be overwhelmed (it will just take longer).

Another important use of a message queue is to tie two disparate systems together. Two applications might be separated not only in time and space but also in platform and language and they can still talk to each other using a message queue. You might be thinking “that’s what a service provides”. But remember that on the other side of the queue there doesn’t have to be a service. Needless to say, the usage of a queue should match the application requirements pretty closely.

To finish up the WCF journey let’s take a look at an implementation of a message queue. The application is an operator console utility that displays the state of various modules that might make up a system. The figure below represents a material handling system that has some robots, AGVs, conveyors, etc. Each of the modules in the system sends their state information to a central monitor application so that the operator can see at any time what each module is doing. Or if something failed s/he can see what immediately preceded the failure. The client modules themselves might be running at different locations and could have been implemented in different languages.

Each module is actually sending its state information to a queue and the monitor service processes the messages that are in the queue. As you’ll see, the client code does not provide any indication that the message is actually going to a queue. At the risk of overstating it...to me that is the beauty of WCF. Additionally, since the use of a message queue is simply defined as the endpoint, at some future time the system could be changed so that it did not use a queue but made direct calls to a service and the client code would not change at all. All that would need to change would be the configuration files to specify a different endpoint.

mathandling.gif

So a message queue behaves like Email, somebody sends you a message and it’s held somewhere until you go get it. Previously, if you implemented a message queue you had two choices in how you processed the messages. You could set up an event handler and the system would call your delegate any time there was a message in the queue or you could poll the message queue if you were looking for a specific message type. Under WCF using a message queue is completely transparent. Because outside of setting up the actual queue, there is no syntactical difference from the other transport types. On the client side you just make a method call as always. And on the service side you define an interface and a class that implements the interface just as you would for any of the other transport types. The only difference between this transport and the others is that a message queue has to exist in order to use it. OK, lets look at some of the code.

The QState Monitor is an MDI app that creates a window for each module that sends it a message. Any subsequent messages that are received from the same module will be displayed in the same window. The message is just two strings, one is the name of the module and the second string is the state information to be displayed. The download contains all of the code so we’ll just look at the WCF side of things. First let’s define the service.

[ServiceContract]
public interface IQSMonitor
{
    [OperationContract(IsOneWay = true)]
    void ModState(string strMod, string strState);
}

[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
public class QSMonitor : IQSMonitor
{
    QStateWnd parent = null;
    public QSMonitor(QStateWnd parent)
    {
        this.parent = parent;
    }
    public void ModState(string strMod, string strState)
    {
        parent.ProcessMessage(strMod, strState);
    }
}

As you can see there is nothing in the code that indicates that a message queue is being used. The actual message queue can be created programmatically or manually using the Computer Management Console (in Administrative Tools). If you create the queue manually then you can change the transport without affecting the code at all. Here’s the code for starting up the service.

...
ServiceHost host = null;
QSMonitor handler;

public QStateWnd()
{
//          To create the queue programmatically uncomment these lines.//          string queueName = ConfigurationManager.AppSettings["queueName"];//          if (!MessageQueue.Exists(queueName))//              MessageQueue.Create(queueName, false);

    handler = new QSMonitor(this);

    // Get base address for Msmq
    string baseAddress = ConfigurationManager.AppSettings["baseAddress"];
    host = new ServiceHost(handler, new Uri(baseAddress));
    // Open the ServiceHostBase to create listeners and start listening for // messages.
    host.Open();
    ...
}

There is nothing different here than the code required for the other transports that we used previously. The handler is the service class. We pass the handler a reference to the main window (QStateWnd) so that it can tell the main window when messages are received. The functionality to be performed for each message is actually defined in the QStateWnd class which is to display the message. Finally we just have to create a ServiceHost to host the service just as before.

The rest of the code is concerned with the mechanics of the application and is pretty straightforward. When the service class receives a message it just passes it to the main window, MDI frame. MDI frame classes maintain a list of the child windows that they own. We use that facility to keep track of what modules have already sent messages. Essentially we just iterate through the list of child windows and check their title bar text. When an MDI child window is created we assign the module name to the window title. This way we can match up the module that sent the message to a specific window.

That’s it, we create a new child window if the module name does not exist on any current window and if there already is an existing window we just add the state information to the window.

 qstate.GIF

Finally here’s what the config file looks like when using a message queue.

Collapse
<configuration>
  <appSettings>
    <!-- use appSetting to configure MSMQ queue name -->
    <add key="queueName" value=".\private$\QSMonitor" />
    <add key ="baseAddress" value="net.msmq://localhost/private/QSMonitor"/>
  </appSettings>

  <system.serviceModel>
    <services>
      <service 
          name="QSMonitor">
        <!-- Define NetMsmqEndpoint -->
        <endpoint address="net.msmq://localhost/private/QSMonitor"
                   binding="netMsmqBinding"
                   bindingConfiguration="Binding1" 
                  contract="IQSMonitor" />
      </service>
    </services>

    <bindings>
      <netMsmqBinding>
        <binding name="Binding1" exactlyOnce="false">
          <security mode="None" >
          </security>
        </binding>
      </netMsmqBinding>
    </bindings>
  </system.serviceModel>
</configuration>

A couple of comments on the config file above because it’s pretty powerful stuff. I have not mentioned the binding classes much because there is just so much stuff there and the default classes provided by WCF have been sufficient. But you can create your own custom binding as well as customize the default binding classes to suit your requirement. Providing that capability programmatically is a very flexible feature but not surprising. What is really neat is that you have the same capabilities when using the configuration file route. In the above file we specified that we wanted to use the default netMsmqBinding binding. You’ll notice that there is an additional bindings section also in the file and that there is an additional property in the endpoint definition labeled-bindingConfiguration. What is happening here is that we are saying ‘use the default netMsmqBinding binding but with the following changes’. So if some of the default options of a particular binding are not suitable you can modify it from the configuration file! Why is this so amazing to me? It could be that I'm amazed by simple things or it could be that this gives us just a little more flexibility in our solution. If we can change something without having to re-compile, that's convenient. If it's already deployed, it's a life saver. And finally, there's a lot of functionality behind these options that we did not have to write a single line of code.

So why didI need to revise the default behavior? Well it wasn’t working. I tried a couple of options and then did some searching on the Net. It turns out that security is turned on by default for this binding. Security was not a requirement for me at that point (I’m sure I’ll have to cross that bridge soon enough). In any case all I had to do was to override the default settings to turn security off. That’s how we learn things most of times isn’t it? Through mistakes that lead to discoveries.

Anyway back to the current example. On the client side all that is needed is a simple proxy and the appropriate entries in the config file. Since we don’t have a service dll and since our service does not have a MEX endpoint, our only option is to roll our own proxy by hand. By the way, you can even create your proxies dynamically by using the ChannelFactory class. Generally there’s more than one way to do something in WCF. How’s that for flexibility?

A quick side note. If you do edit the proxies make sure that your parameter names match up on both sides. Parameters are identified by name, so if there is just the smallest typo, you’ll spend some time looking for why messages are not being processed. Even though they are being sent correctly and there are no exceptions. A free debugging option when using a message queue is the message queue itself! You can examine your messages in the message queue, if the service is not running. For the other transports you need some other utilities to be able to see the messages as they are crossing the wire.

Here’s the proxy and config file that the clients will need. There is a sample client in the download source.

[ServiceContract]
public interface IQSMonitor
{
[OperationContract(IsOneWay = true)]
void ModState(string strMod, string strState);
}
public interface IQSMonitorChannel : IQSMonitor, 
                                     System.ServiceModel.IClientChannel
{
}
public partial class QSMonitorProxy : System.ServiceModel.ClientBase, 
                                      IQSMonitor
{
    public QSMonitorProxy()
    {
    }
    public void ModState(string strMod, string strState)
    {
        base.InnerProxy.ModState(strMod, strState);
    }
}
<configuration>
  <system.serviceModel>
    <client>
      <endpoint name=""
                address="net.msmq://localhost/private/QSMonitor" 
                  binding="netMsmqBinding" 
                  bindingConfiguration="Binding1" 
                contract="IQSMonitor" />
    </client>
    <bindings>
      <netMsmqBinding>
        <binding name="Binding1" exactlyOnce="false">
          <security mode="None">
          </security>
        </binding>
      </netMsmqBinding>
    </bindings>
  </SYSTEM.SERVICEMODEL>
</configuration>

If you feel like experimenting, make the following change to the config file for both the service and the client.

    <add key ="baseAddress" value="net.pipe://localhost/QSMonitor"/>
    ...
    <endpoint address="net.pipe://localhost/QSMonitor"
               binding="netNamedPipeBinding"
              contract="IQSMonitor" />
    ...

Re-start the service and the client. You should see no difference in the results but what is happening under the cover changed quite a bit.

Heigh-Ho Silver Away!

There you have it, just enough knowledge on WCF to make you slightly dangerous. There's lots more to explore, there's the security side of communication, all the binding options, peer-to-peer and broadcast messages, and maybe even defining custom transports. You never know when that special requirement comes up that just does not fit the mold. So until next time Kemo Sabe...

Al Alberto


Click here to view Al Alberto's online profile.

只有注册用户登录后才能发表评论。