Building a Custom File Transport, Part 10: Request Context

Today is the final day of coding for the file transport. The last piece is the request context for the server. A request context forces the correlation between client and server messages in the request-reply message pattern. One thing that differentiates creating a request context from the other pieces we've looked at is that there is no RequestContext base class. You have to directly implement the IRequestContext interface, which means there's a little more work here.

 using System;
using System.ServiceModel;
using System.ServiceModel.Channels;

namespace FileTransport
{
   partial class FileReplyChannel
   {
      class FileRequestContext : IRequestContext
      {
         bool aborted;
         readonly Message message;
         readonly FileReplyChannel parent;
         CommunicationState state;
         readonly object thisLock;
         readonly object writeLock;

         public FileRequestContext(Message message, FileReplyChannel parent)
         {
            this.aborted = false;
            this.message = message;
            this.parent = parent;
            this.state = CommunicationState.Opened;
            this.thisLock = new object();
            this.writeLock = new object();
         }

         public void Abort()
         {
            lock (thisLock)
            {
               if (this.aborted)
               {
                  return;
               }
               this.aborted = true;
               this.state = CommunicationState.Faulted;
            }
         }

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

         public IAsyncResult BeginReply(Message message, AsyncCallback callback, object state)
         {
            return BeginReply(message, this.parent.DefaultSendTimeout, callback, state);
         }

         public void Close(TimeSpan timeout)
         {
            lock (thisLock)
            {
               this.state = CommunicationState.Closed;
            }
         }

         public void Close()
         {
            Close(this.parent.DefaultCloseTimeout);
         }

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

         public void Reply(Message message, TimeSpan timeout)
         {
            lock (thisLock)
            {
               if (this.aborted)
               {
                  throw new CommunicationObjectAbortedException();
               }
               if (this.state == CommunicationState.Faulted)
               {
                  throw new CommunicationObjectFaultedException();
               }
               if (this.state == CommunicationState.Closed)
               {
                  throw new ObjectDisposedException("this");
               }
            }
            this.parent.ThrowIfDisposedOrNotOpen();
            lock (writeLock)
            {
               this.parent.WriteMessage(FileChannelBase.PathToFile(this.parent.LocalAddress.Uri, "reply"), message);
            }
         }

         public void Reply(Message message)
         {
            Reply(message, this.parent.DefaultSendTimeout);
         }

         public Message RequestMessage
         {
            get { return message; }
         }

         public void Dispose()
         {
            Close();
         }
      }
   }
}

Probably the biggest additional work item is the need to manually track the state of the request context. This state machine tracking is what you normally get for free from a CommunicationObject. Like most of the other classes in the example, I've kept this pretty simple to reduce the size of the example.

Otherwise, the implementation of the request context is straightforward. The goal here is to supply something that looks a lot like a channel for the reply to be sent through. I've embedded the request context as a partial class of the parent channel although that is entirely optional. You can design the request context to be independent of its channel if you want.

Although we're done with the code and you can run both the client and server now, there's one more article in this series coming next week to wrap things up. This will talk about what you'd have to do to make this simple file transport ready for production use.

Next time: Using Impersonation with Transport Security