The request channel is what's actually going to send messages from the client and receive a response from the server. In the request-reply channel shape, sending and receiving messages are inextricably tied together. That means the client just has to implement this one Request operation. As I mentioned at the beginning, this sample keeps things simple by only implementing the synchronous version of the communication methods. That means we'll send a request to the server and then wait for either a response to come back or for the operation timeout to expire.

using System;
using System.IO;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.Threading;

namespace FileTransport
{
   class FileRequestChannel : FileChannelBase, IRequestChannel
   {
      readonly Uri via;
      readonly object writeLock;

      public FileRequestChannel(BufferManager bufferManager, MessageEncoderFactory encoderFactory, EndpointAddress address,
         FileRequestChannelFactory parent, Uri via)
         : base(bufferManager, encoderFactory, address, parent, parent.Streamed, parent.MaxReceivedMessageSize)
      {
         this.via = via;
         this.writeLock = new object();
      }

      public IAsyncResult BeginRequest(Message message, TimeSpan timeout, AsyncCallback callback, object state)
      {
         throw new Exception("The method or operation is not implemented.");
      }

      public IAsyncResult BeginRequest(Message message, AsyncCallback callback, object state)
      {
         return BeginRequest(message, DefaultReceiveTimeout, callback, state);
      }

      public Message EndRequest(IAsyncResult result)
      {
         throw new Exception("The method or operation is not implemented.");
      }

      public Message Request(Message message, TimeSpan timeout)
      {
         ThrowIfDisposedOrNotOpen();
         lock (this.writeLock)
         {
            try
            {
               File.Delete(PathToFile(Via, "reply"));
               using (FileSystemWatcher watcher = new FileSystemWatcher(Via.AbsolutePath, "reply"))
               {
                  ManualResetEvent replyCreated = new ManualResetEvent(false);
                  watcher.Changed += new FileSystemEventHandler(
                     delegate(object sender, FileSystemEventArgs e) { replyCreated.Set(); }
                  );
                  watcher.EnableRaisingEvents = true;
                  WriteMessage(PathToFile(via, "request"), message);
                  if (!replyCreated.WaitOne(timeout, false))
                  {
                     throw new TimeoutException(timeout.ToString());
                  }
               }
            }
            catch (IOException exception)
            {
               throw ConvertException(exception);
            }
            return ReadMessage(PathToFile(Via, "reply"));
         }
      }

      public Message Request(Message message)
      {
         return Request(message, DefaultReceiveTimeout);
      }

      public Uri Via
      {
         get { return this.via; }
      }
   }
}

This is a file transport so we need to use the file system to signal when messages are sent. The procedure that this transport uses is to create a file system watcher for a particular file URI so that we're notified when a message arrives. The channel waits on the file system watcher for only a limited time to implement the timeout mechanism.

This doesn't perfectly fulfill the requirements of the timeout model because we're neglecting to measure how much time is spent sending the message. Unfortunately, we haven't shipped a good sample yet for measuring operation times to use in a situation like this. In this case, it rarely makes a difference due to the speed of file transfers, but a robust custom transport can't get away with this. The right solution is to time the entire sequence of IO operations so that the event is fired when the timeout is reached. We'd also have to have some way of cancelling the operation that was in progress when the event occurs.

Next time: Basics of Transport Security