Constructing the REST RSS Server


Complete the steps in the following sections to construct a REST RSS server.

Create the RSS Feeds

The first step is to create the RSS feeds that the server will publish:

  1. Copy the code associated with this chapter that you downloaded from www.samspublishing.com to the folder C:\WCFHandsOn. The code is all in a folder called RepresentationalStateTransfer, and it contains a single Visual Studio solution with the same name. After the code has been unzipped, there should be a folder that looks like the one shown in Figure 13.1.

    Figure 13.1. Representational State Transfer solution folder.

  2. Open the solution C:\WCFHandsOn\RepresentationalStateTransfer\RepresentationalStateTransfer.sln. The solution includes the projects SafeFeeder, which is for building the REST RSS service that delivers RSS feeds with digitally signed content ratings, and SafeReader, which is for building the child's RSS aggregator that can process those ratings and behave in accordance with them.

  3. Examine the file Feed.xml in the SafeFeeder project:

    <?xml version="1.0"?> <rss version="2.0" xmlns:rating="http://blog.code.com/ContentRating">        <channel>                <title>"Harry Potter" author's homepage.</title>                <link>http://www.jkrowling.com/textonly/en/</link>                <description>See the homepage of the author                       of the Harry Potter! books.</description>                <language>en-us</language>               <rating:contentRating>AllAges</rating:contentRating>        </channel>  </rss>

    The only formal stipulation on the addition of extensions to RSS made by the RSS 2.0 specification is that the extension tags are included in their own namespace (Winer 2005). Thus, the declaration of a namespace for the content rating extensions, http://blog.code.com/ContentRating, appears in the RSS file, along with the content rating element itself. The feed defined by this RSS file is rated AllAges, signifying that the RSS feed is fine for children to see.

  4. Now look at the file NaughtyFeed.xml, in the SafeFeeder project:

    <?xml version="1.0"?> <rss version="2.0" xmlns:rating="http://blog.code.com/ContentRating">        <channel>                <title>"Jessica Rabbit" from "Who Framed Roger Rabbit?"</title>                <link>http://www.retrocrush.com/babes/babes2004/jessicarabbit/01.jpg                </link>                <description>A picture of Jessica Rabbit.</description>                <language>en-us</language>                <rating:contentRating>AdultsOnly</rating:contentRating>        </channel> </rss>

    This RSS file contains a link to a picture of Jessica Rabbit, a corrupting influence if there ever was one. So it is rated AdultsOnly, to indicate that it is not suitable for viewing by children.

Define the Contract for the REST RSS Service

The REST RSS service will have a contract, like any other Windows Communication Foundation service:

  1. Add a class module called IReallySimpleSyndication.cs to the SafeFeeder project.

  2. Modify the contents of the file so that it looks like this:

    using System; using System.ServiceModel; namespace RepresentationalStateTransfer {      [ServiceContract]      public interface IReallySimpleSyndication      {          [OperationContract(Action="*")]          Message Get(Message input);       } }

    IReallySimpleSyndication is a common .NET interface. The addition of the Windows Communication Foundation's ServiceContract attribute identifies it as a Windows Communication Foundation contract.

    In Chapter 10, "Publish/Subscribe Systems," it was explained that specifying Action="*" as the parameter to the OperationContract attribute signifies that the operation with that attribute is the unmatched message handler, which means that operation will be used to process all messages that are not matched with another operation. A REST service like the RSS service being constructed here must provide an unmatched message handler. The SOAP specifications include HTTP bindings that provide standard ways of expressing, within the header of an HTTP POST method, which operation on the server is the intended destination of the message, although different versions of the SOAP specification provide different ways of incorporating that information. REST does not provide any such standard mechanism for mapping incoming messages to service operations, so all the messages directed at the REST service need to be processed by the same handler, which will be the unmatched message handler.

    The Windows Communication Foundation's Message class represents messages as messages, rather than as messages of a particular type. WSDL provides a standard way, which leverages the XML Schema language, of specifying the types of messages processed by a given operation. REST has no such mechanism, so in constructing REST services, one must resort to using the Message class to represent the messages. Thus, the definition of the Get() operation that will process the messages of the REST service represents the inputs and outputs of the method simply as instances of the Message class.

Implement the Contract for the REST RSS Service

Having defined the contract, the next step is to implement it:

  1. Add a class module called ReallySimpleSyndicationServer.cs to the SafeFeeder project. Modify the file so that it looks like Listing 13.1. The code in Listing 13.1 is provided in the file C:\WCFHandsOn\RepresentationalStateTransfer\SafeFeeder\ReallySimpleSyndication.txt.

    Listing 13.1. The REST RSS Service Type

    using System.IO; using System.ServiceModel; using System.Text; using System.Xml; namespace RepresentationalStateTransfer {     public class ReallySimpleSyndicationServer : IReallySimpleSyndication     {         private string feedFilePath = null;         public ReallySimpleSyndicationServer(string feedFilePath)         {             this.feedFilePath = feedFilePath;         }         private ReallySimpleSyndicationServer()         {         }         Message IReallySimpleSyndication.Get(Message input)         {             Message message = null;             if (File.Exists(this.feedFilePath))             {                 XmlDocument document = new XmlDocument();                 document.Load(this.feedFilePath);                 ReallySimpleRatedSyndication.AddContentRating(document);                 message = Message.CreateMessage(                     "*",                     new XmlNodeReader(document.DocumentElement));           }           else           {               message = Message.CreateMessage(Constants.DefaultAction);            }            return message;         }      }  }

    In the argot of the Windows Communication Foundation, a class that implements an interface with the Windows Communication Foundation's ServiceContract attribute is a service type. The interface IReallySimpleSyndication, which was added in the preceding step, has that attribute. So the class that we have added now, ReallySimpleSyndicationServer, to implement that interface is a service type, the service type for the REST RSS service.

    In its implementation of the IReallySimpleSyndication interface's lone Get() method, ReallySimpleSyndicationServer loads one of the two RSS files that we looked at earlier, and calls on the static AddSignedContentRating() method of the ReallySimpleSyndication class to sign the content rating and the link in the RSS file. Then it creates a message with the RSS file incorporating the signed content rating and link as the body, and returns that message to the caller.

  2. Examine the AddSignedContentRating() method of the ReallySimpleRatedSyndication class, which is in the ReallySimpleRatedSyndication.cs module of the ReallySimpleRatedSyndication project, and reproduced in Listing 13.2.

    Listing 13.2. Method for Adding a Content Rating

    using System; using System.Collections.Generic; using System.IO; using System.Security.Cryptography; using System.Security.Cryptography.Xml; using System.Security.Cryptography.X509Certificates; using System.Text; using System.Xml; namespace RepresentationalStateTransfer {     public enum ContentRating     {          Unknown = -1,          AllAges = 0,          AdultsOnly = 1      }      public class ReallySimpleRatedSyndication      {          private const string personalCertificateStoreLocation              = @"My";          private const string trustedPeopleCertificateStoreLocation              = @"TrustedPeople";          private const string contentRatingCertificateSubjectName              = @"Woodgrove";          private const string contentRatingExtensionTagName              = @"contentRating";          private const string linkTagName              = @"link";          private const string signedContentRatingExtensionTagName              = @"signedContentRating";          private const string contentRatingExtensionNamespace              = @"http://blog.code.com/ContentRating";          private const string signatureValueTagName              = "SignatureValue";          private const string contentRatingIdentifier              = "ContentRatingIdentifier";          private const string linkIdentifier              = "LinkIdentifier";          private const string universalResourceIdentifierPrefix              = @"#";          private const string signatureTagName              = @"Signature";          private const string xmlDigitalSignatureNamespace              = @"http://        www.w3.org/2000/09/xmldsig#";          private static XmlElement CreateWrappedSignature(              AsymmetricAlgorithm signingKey,              XmlElement contentToSign,              string identifier)          {          SignedXml signedXML = new SignedXml(contentToSign);             signedXML.SigningKey = signingKey;             DataObject dataObject = new DataObject(                 identifier,                 null,                 null,                 contentToSign);            signedXML.AddObject(dataObject);            Reference reference = new Reference();            reference.Uri = string.Concat(                ReallySimpleRatedSyndication.universalResourceIdentifierPrefix,                identifier);            signedXML.AddReference(reference);            signedXML.ComputeSignature();            return signedXML.GetXml();           }          private static bool VerifySignature(              X509Certificate2 certificate,              XmlElement signatureElement)           {              SignedXml signedXML = new SignedXml(signatureElement);              signedXML.LoadXml(signatureElement);              bool checks = signedXML.CheckSignature(certificate, true);              return checks;            }            public static void AddSignedContentRating(XmlDocument document)            {                X509Store store = new X509Store(                    ReallySimpleRatedSyndication.personalCertificateStoreLocation,                    StoreLocation.LocalMachine);                store.Open(OpenFlags.ReadOnly);                X509CertificateCollection certificateCollection =                    store.Certificates.Find(                       X509FindType.FindBySubjectName,        ReallySimpleRatedSyndication.contentRatingCertificateSubjectName,                            false);                    X509Certificate2 certificate =                        (X509Certificate2)certificateCollection[0];                    XmlElement ratingElement =                        (XmlElement)document.GetElementsByTagName(                            ReallySimpleRatedSyndication.contentRatingExtensionTagName,       ReallySimpleRatedSyndication.contentRatingExtensionNamespace).Item(0);                   XmlElement signedElement =                       ReallySimpleRatedSyndication.CreateWrappedSignature(                            certificate.PrivateKey,                            ratingElement,                            ReallySimpleRatedSyndication.contentRatingIdentifier);                   ratingElement.InnerXml = signedElement.OuterXml;                   XmlElement linkElement =                       (XmlElement)document.GetElementsByTagName(                            ReallySimpleRatedSyndication.linkTagName).Item(0);                   signedElement = ReallySimpleRatedSyndication.CreateWrappedSignature(                        certificate.PrivateKey,                        linkElement,                        ReallySimpleRatedSyndication.linkIdentifier);                   linkElement.InnerXml = signedElement.OuterXml;        }          public static bool ValidateSignedContentRating(              XmlDocument document,              out ContentRating rating,              out string link)          {              rating = ContentRating.Unknown;              link = string.Empty;              X509Store store = new X509Store(       ReallySimpleRatedSyndication.trustedPeopleCertificateStoreLocation,                       StoreLocation.CurrentUser);                   store.Open(OpenFlags.ReadOnly);                   X509CertificateCollection certificateCollection =                       store.Certificates.Find(                           X509FindType.FindBySubjectName,      ReallySimpleRatedSyndication.contentRatingCertificateSubjectName,                           true);                 X509Certificate2 certificate =                      (X509Certificate2)certificateCollection[0];                 XmlNodeList nodes =                     document.GetElementsByTagName(                         ReallySimpleRatedSyndication.signatureTagName,                         ReallySimpleRatedSyndication.xmlDigitalSignatureNamespace);                 foreach(XmlNode signedNode in nodes)                 {                       if(!(ReallySimpleRatedSyndication.VerifySignature(                         certificate,(XmlElement)signedNode)))                       {                         return false;                        }                      }                      nodes = document.GetElementsByTagName("Object");                      foreach (XmlNode node in nodes)                      {                           foreach (XmlAttribute attribute in node.Attributes)                           {                                switch (attribute.Value)                                {                                    case "ContentRatingIdentifier":                                       rating =                                           (ContentRating)Enum.Parse(                                                typeof(ContentRating),                                                ((XmlElement)node).InnerText);                                         break;                                    case "LinkIdentifier":                                       link = ((XmlElement)node).InnerText;                                          break;                                    }                                }                            }                            return true;                      }                  }               }

    The method retrieves an X.509 certificate from the My store location within the local machine store, and uses the private key of that certificate to sign the content rating. The signed content rating is added to the RSS feed document as the contents of the contentRating element.

  3. Install the Woodgrove certificate used by the ReallySimpleRatedSyndication class to sign the content ratings of the RSS feeds by executing the batch file C:\WCFHandsOn\RepresentationalStateTransfer\SetUp.bat. It is assumed that the certificate belongs to an organization that parents will trust to certify the content ratings of RSS feeds. The Setup.bat batch file assumes that the tools included with the version of the Microsoft Windows SDK for use with WinFX are installed in the folder C:\Program Files\Microsoft SDKs\Windows\v1.0\Bin. If they are not installed there, modify the batch file accordingly. If their location is unknown, search the hard disks for the tool CertKeyFileTool.exe; the other tools should be in the same location. A second batch file, C:\WCFHandsOn\RepresentationalStateTransfer\CleanUp.bat, is provided for removing the certificate after the exercise has been completed.

Host the REST RSS Service in an Application

A host is provided for the REST RSS service type in these next few steps:

1.

Open the file PlainXMLRepresentationalStateTransferServiceHost.cs, in the SafeFeeder project, and modify the Main() method of the PlainXMLRepresentationalStateTransferServiceHost class so that it reads as shown here:

static void Main(string[] arguments) {      ReallySimpleSyndicationServer serviceType           = new ReallySimpleSyndicationServer(arguments[0]);      ServiceHost service = new ServiceHost(            serviceType, new Uri[] {new Uri(@"http://localhost:8888/") });            service.Description.Behaviors.Remove(                typeof(ServiceMetadataBehavior));            service.Open();            Console.WriteLine(                @"The service is available. Press any key to terminate it.");            Console.ReadLine();          }


The additional code uses the Windows Communication Foundation's ServiceHost class to host the ReallySimpleSyndicationServer service type within the SafeFeeder console application.

The base address of the service is given as http://localhost:8888/. Shortly, a specific address will be added, relative to that base address, for messages destined to be processed by the operations of the IReallySimpleSyndication contract of the REST RSS service.

2.

Notice the statement

service.Description.Behaviors.Remove(     typeof(ServiceMetadataBehavior));


By default, the Windows Communication Foundation responds to an HTTP GET method directed at the address of a service with the WSDL for the service. That behavior is facilitated by the Windows Communication Foundation's ServiceMetadataBehavior class. Now, not only is there no way to describe a REST service like the REST RSS service with WSDL, but HTTP GET methods directed at the address of our service should also not be intercepted by the ServiceMetadataBehavior class. Those commands should pass through to the service. So the role of the statement

service.Description.Behaviors.Remove(     typeof(ServiceMetadataBehavior));


is to disable the ServiceMetadataBehavior class for the REST RSS service.

Is it unfortunate that one should have to explicitly disable the ServiceMetadataBehavior class in order to build a REST service with the Windows Communication Foundation? Well, having to do so aptly illustrates the nature of the choice one makes in adopting REST to build services. Rich tools for building SOAP services have been freely available for several years. Those tools not only support SOAP and its extensions, but also automate the process of providing perfectly accurate machine-readable descriptions of services in WSDL, and automate the process of retrieving those descriptions and generating clients from them. Given all of that, the choice of eschewing SOAP services in favor of REST is a deliberate one to forego a wealth of features. So having to deliberately turn off the feature by which the Windows Communication Foundation automatically provides WSDL as metadata to describe services, in order to build a REST service, seems most appropriate. Think of it as a final warning.

Configure the Address and the Binding of the Service

The next steps are to specify an address and a binding for the REST RSS service:

1.

Add an application configuration file called App.Config to the SafeFeeder project, and modify its contents as shown in Listing 13.3.

Listing 13.3. REST RSS Service Configuration

<?xml version="1.0" encoding="utf-8" ?> <configuration>     <system.serviceModel>         <services>              <service type= "RepresentationalStateTransfer.ReallySimpleSyndicationServer,SafeFeeder">                 <endpoint                        address="Service"                        binding="customBinding"                        bindingConfiguration=                            "PlainXMLRepresentationalStateTransferBinding"                        contract= "RepresentationalStateTransfer.IReallySimpleSyndication, SafeFeeder"/>             </service>         </services>        <bindings>               <customBinding>                    <binding name="PlainXMLRepresentationalStateTransferBinding">                         <plainXMLEncoder/>                         <httpTransport mapAddressingHeadersToHttpHeaders="true"/>                    </binding>               </customBinding>         </bindings>         <extensions>               <bindingElementExtensions>                    <add name="plainXMLEncoder"                type= "System.ServiceModel.PlainXMLEncoderBindingElementExtensionSection,                      PlainXMLRepresentationalStateTransfer"/>             </bindingElementExtensions>         </extensions>     </system.serviceModel> </configuration>

Here the specific address, Service, is provided for messages meant to be processed by the operations of the IReallySimpleSyndication contract. That address is relative to the base address of http://localhost:8888/ that was specified for the service earlier, in providing the host. So the absolute address for messages to be processed by the operation of the IReallySimpleSyndication contract is http://localhost:8888/Service.

As it was just explained, because all REST services use HTTP to exchange messages, in configuring the binding for the REST RSS service, it will be necessary to specify the use of the HTTP protocol to communicate. Also, because the service will be used to publish an RSS feed, it will be necessary to configure the binding for the exchange of plain XML messages. The Windows Communication Foundation does not provide a built-in binding with those characteristics, but it is very easy to create one's own bindings, which are called custom bindings.

The name that is assigned, in the configuration of the REST service, to the custom binding is PlainXMLRepresentationalStateTransferBinding. Recall that bindings are simply a composition of one or more binding elements. Two binding elements are used to compose the PlainXMLRepresentationalStateTransferBinding: one that provides for the exchange of plain XML messages, and another that specifies the use of the HTTP protocol.

Use of the HTTP protocol is specified by including HttpTransportBindingElement within the custom binding. The value of its MapAddressingHeadersToHttpHeaders property must be set to true for REST services. Recall that whereas the SOAP specifications include HTTP bindings that provide standard ways of expressing which operation on the server is the intended destination of the message, REST does not provide any such standard mechanism. Setting MapAddressingHeadersToHttpHeaders to true causes the value of the To property of the Message instance representing an HTTP request to the REST service from a client to be set to the URI of the request. That will, in turn, result in that Message instance being passed to the unmatched message handler of the contract associated with the URI of the request. In the particular case of the REST RSS service, the IReallySimpleSyndication contract has been associated with the address http://localhost:8888/Service, and its unmatched message handler is its Get() method; so, by default, all Message instances with http://localhost:8888/Service as the value of their To property will be sent to the implementation of that method. Because the value of the MapAddressingHeadersToHttpHeaders property of the HttpTransportBindingElement is set to true, when HTTP requests are directed to http://localhost:8888/Service, the Windows Communication Foundation will create instances of the Message class to represent those requests, and set their To properties to http://localhost:8888/Service. Hence, those messages will then be dispatched to the Get() method of ReallySimpleSyndicationServer that implements the IReallySimpleSyndication contract.

The binding element that provides for the exchange of plain XML messages by the REST RSS service is the PlainXMLEncoderBindingElement. As its name implies, it is a binding element that specifies that messages exchanged with the service are to be encoded as plain XML.

The PlainXMLEncoderBindingElement is not among the message encoder binding elements that are built into the Windows Communication Foundation. So a custom one has been constructed using the tools that the Windows Communication Foundation provides for that purpose. Because it is not a built-in binding element, one needs to tell the Windows Communication Foundation where to find it, and that is the purpose of this configuration information that is included in the configuration file in Listing 13.3:

<extensions>     <bindingElementExtensions>          <add name="plainXMLEncoder"     type= "System.ServiceModel.PlainXMLEncoderBindingElementExtensionSection,             PlainXMLRepresentationalStateTransfer"/>     </bindingElementExtensions> </extensions>


It tells the Windows Communication Foundation to use the PlainXMLEncoderBindingElementExtensionSection in the PlainXMLRepresentationalStateTransfer assembly to configure and provide the binding element referred to by the plainXMLEncoder element of the binding provided for the service:

<plainXMLEncoder/>


2.

Examine the PlainXMLEncoderBindingElementExtensionSection class that is in the PlainXMLEncoderBindingElementExtensionSection.cs class module in the PlainXMLRepresentationalStateTransfer project, and also reproduced in Listing 13.4.

Listing 13.4. Plain XML Encoder Binding Element Section

using System; using System.Collections.Generic; using System.ServiceModel.Configuration; using System.Text; namespace System.ServiceModel {     public class PlainXMLEncoderBindingElementExtensionSection:         BindingElementExtensionSection     {         private const string sectionName = @"plainXMLEncoding";         public override Type BindingElementType      {            get            {                return typeof(PlainXMLEncoderBindingElement);            }      }      protected override BindingElement CreateBindingElement()      {           return new PlainXMLEncoderBindingElement();      }      public override string ConfiguredSectionName      {          get          {               return PlainXMLEncoderBindingElementExtensionSection.sectionName;           }       }    } }

The PlainXMLEncoderBindingElementExtensionClass derives from the abstract BindingElementExtensionSection class:

using System; using System.ServiceModel; namespace System.ServiceModel.Configuration {     public abstract class BindingElementExtensionSection :         ServiceModelExtensionSection     {         protected BindingElementExtensionSection();         public abstract Type BindingElementType { get; }         public virtual void ApplyConfiguration(BindingElement bindingElement);         protected internal abstract BindingElement CreateBindingElement();         protected internal virtual void InitializeFrom(             BindingElement bindingElement);     } }


That class defines the abstract method CreateBindingElement(). Therefore, the Windows Communication Foundation knows that the PlainXMLEncoderBindingElementExtensionClass has a CreateBindingElement() method that the Windows Communication Foundation can invoke to retrieve the custom binding element referred to by

<plainXMLEncoder/>


in the configuration of the binding for the service.

3.

Study the PlainXMLEncoderBindingElement class that is in the PlainXMLEncoderBindingElement.cs class module in the PlainXMLRepresentationalStateTransfer project, and also shown in Listing 13.5.

Listing 13.5. Plain XML Encoder Binding Element

public class PlainXMLEncoderBindingElement :      BindingElement,      IMessageEncodingBindingElement {      public override IChannelFactory BuildChannelFactory(          ChannelBuildContext context)      {          if (context == null)              throw new ArgumentNullException("context");          context.UnhandledBindingElements.Add(this);          return context.BuildInnerChannelFactory(); }       public override IChannelListener<TChannel>BuildChannelListener<TChannel>(           ChannelBuildContext context)       {           if (context == null)               throw new ArgumentNullException("context");           context.UnhandledBindingElements.Add(this);           return context.BuildInnerChannelListener<TChannel>();        }        public override bool CanBuildChannelListener<TChannel>(            ChannelBuildContext context)        {            if (context == null)                throw new ArgumentNullException("context");            context.UnhandledBindingElements.Add(this);            return context.CanBuildInnerChannelListener<TChannel>();         }         public override BindingElement Clone()         {             return new PlainXMLEncoderBindingElement();         }         public override ChannelProtectionRequirements GetProtectionRequirements()         {             throw new Exception("The method or operation is not implemented.");         }           #region IMessageEncodingBindingElement Members           AddressingVersion IMessageEncodingBindingElement.AddressingVersion           {               get               {                    throw new Exception("The method or operation is not implemented.");                }            }           MessageEncoderFactory               IMessageEncodingBindingElement.CreateMessageEncoderFactory()           {               return new PlainXMLMessageEncoderFactory();           }          #endregion        }

This class is the custom binding element for supporting plain XML messages. As any binding element does, it derives from the abstract base class for binding elements, which is BindingElement:

public abstract class BindingElement { protected BindingElement(); protected BindingElement(BindingElement other); public virtual IChannelFactory BuildChannelFactory(      ChannelBuildContext context); public virtual IChannelFactory<TChannel> BuildChannelFactory<TChannel>(      ChannelBuildContext context); public virtual bool CanBuildChannelFactory<TChannel>(      ChannelBuildContext context);      public abstract BindingElement Clone();      public virtual T GetCapabilities<T>(          IList<BindingElement> lowerBindingElements) where T : class;      public abstract ChannelProtectionRequirements GetProtectionRequirements();      public virtual ChannelProtectionRequirements GetProtectionRequirements(          CustomBinding context); }


As a non-abstract derivative of BindingElement, PlainXMLEncoderBindingElement must implement the abstract methods of BindingElement. The implementations that it provides do no more than what is necessary to include the capability of processing plain XML documents within the sequence of binding elements that are applied in processing incoming or outgoing messages.

As any binding element that is for message encoding does, PlainXMLEncoderBindingElement implements the interface IMessageEncodingBindingElement:

public interface IMessageEncodingBindingElement {     AddressingVersion AddressingVersion { get; }     MessageEncoderFactory CreateMessageEncoderFactory(); }


The implementation of the CreateMessageEncoderFactory() method of that interface returns an instance of a MessageEncoderFactory derivative called PlainXMLMessageEncoderFactory, which the Windows Communication Foundation uses to construct plain XML message encoders to process incoming and outgoing messages.

4.

Look at the PlainXMLMessageEncoderFactory class in the PlainXMLBindingElement.cs class module of the PlainXMLRepresentationalStateTransfer project:

public class PlainXMLMessageEncoderFactory : MessageEncoderFactory {      public override MessageEncoder Encoder      {          get          {              return new PlainXMLEncoder();           }       }       public override MessageVersion MessageVersion       {           get           {                return MessageVersion.Soap11Addressing1;           }        }     }


The PlainXMLMessageEncoderFactory class overrides the base MessageEncoderFactory class' MessageEncoder property to allow the Windows Communication Foundation to retrieve instances of PlainXMLEncoder, a custom encoder for encoding messages in plain XML.

An encoder in the Windows Communication Foundation has two duties. One is to accept a stream or an array of bytes that have been received, and to yield from those an instance of the Message class, which will be the form in which the data that has been received will be processed internally by the Windows Communication Foundation. The other duty of an encoder is to accept instances of the Message class representing output, and to transform those into an array or a stream of bytes.

5.

Examine the ReadMessage() of the PlainXMLEncoder class in the PlainXMLBindingElement.cs file of the PlainXMLRepresentationalStateTransfer project. It is shown in Listing 13.6. It implements the abstract ReadMessage() method of the PlainXMLEncoder's abstract base MessageEncoder class.

Listing 13.6. The Plain XML Encoder's ReadMessage() Method

public override Message ReadMessage(     ArraySegment<byte> buffer,     BufferManager bufferManager) {     Message message = null;     if (buffer.Count > 0)     {         MemoryStream stream = new MemoryStream(buffer.Array);         XmlTextReader reader = new XmlTextReader(stream);         message = new PlainXMLMessage(             MessageVersion.Soap11Addressing1,             "*",             reader);         return message; }     message = new PlainXMLMessage(         MessageVersion.Soap11Addressing1,         "*");     return message; }

The ReadMessage() method is invoked to transform received data into an instance of the Message class. The method takes the array of bytes that it receives, which it is expecting to be plain XML, and creates from those an instance of the PlainXMLMessage class, which is a derivative of the Message class, reproduced in Listing 13.7.

Listing 13.7. Plain XML Message Type

public class PlainXMLMessage : Message {     private const string MissingRootError = "Root element is missing.";     private MessageHeaders headers;     private MessageProperties properties;     private XmlReader reader;     public PlainXMLMessage(MessageVersion version, string action, XmlReader body)         : base()     {          headers = new MessageHeaders(version);          properties = new MessageProperties();          properties.Add(              Constants.HttpRequestMessageProperty,              new HttpRequestMessageProperty());          reader = body;     }     public PlainXMLMessage(MessageVersion version, string action)         : base()     {         headers = new MessageHeaders(version);         properties = new MessageProperties();     }     public override MessageHeaders Headers     {         get     {         return headers;     } } protected override void OnWriteBodyContents(XmlDictionaryWriter writer) {     if (reader != null)     {         while (!reader.EOF)         {             try             {                writer.WriteNode(reader, true);             }             catch (XmlException exception)             {                  if (exception.Message.Contains(                      PlainXMLMessage.MissingRootError))                  {                      return;                  }                  else { throw exception; }             }         }     } } public override MessageProperties Properties {     get     {         return properties;     } } public override MessageVersion Version {     get     {         return Headers.MessageVersion;     }   } }

From the constructor of that class,

public PlainXMLMessage(MessageVersion version, string action, XmlReader body)         : base() {     headers = new MessageHeaders(version);     properties = new MessageProperties();     properties.Add(         Constants.HttpRequestMessageProperty,         new HttpRequestMessageProperty());     reader = body; }


it is apparent that all the data read from the array of incoming bytes is assigned to the field that represents the body of the message. That is appropriate, because, as a plain XML message rather than a SOAP message, none of the data in the message should constitute a SOAP header.

6.

Examine the WriteMessage() methods of the PlainXMLEncoder class in the PlainXMLBindingElement.cs file of the PlainXMLRepresentationalStateTransfer project. It is reproduced in Listing 13.8. It implements the abstract WriteMessage() method of the PlainXMLEncoder's abstract base MessageEncoder class.

Listing 13.8. The Plain XML Encoder's WriteMessage() Method

public override ArraySegment<byte> WriteMessage(     Message message,     int maxMessageSize,     BufferManager bufferManager,     int messageOffset) {     byte[] buffer = bufferManager.TakeBuffer(maxMessageSize);     MemoryStream stream = new MemoryStream(buffer);     XmlTextWriter innerWriter = new XmlTextWriter(stream, Encoding.UTF8);     XmlDictionaryWriter outerWriter =     XmlDictionaryWriter.CreateDictionaryWriter(         innerWriter,         false);     message.WriteBodyContents(outerWriter);     outerWriter.Flush();     int position = (int)stream.Position;     outerWriter.Close();     return new ArraySegment<byte>(buffer, 0, position); }

The WriteMessage() method of the PlainXMLEncoder is invoked to turn outgoing instances of the Message class into an array of bytes. To produce an array of bytes containing nothing besides plain XML, the method only serializes the body of the message into the array of bytes. Any headers incorporated in the Message instance are ignored, because, as a plain XML message rather than a SOAP message, the message should not contain any headers.

See a Windows Communication Foundation Plain XML REST Service in Action

To use the REST RSS service, follow these simple steps:

1.

Compile the SafeFeeder project.

2.

Right-click on the project and choose Debug and then Start New Instance from the context menu. Doing so should serve to start the console application that has been configured to host the REST RSS service.

3.

When there is some activity in the console of the SafeFeeder application, open a browser and browse to http://localhost:8888/Service. One of the RSS feeds should appear in the browser, as shown in Figure 13.2, the browser having sent an HTTP GET method request to the service, which responded with the plain XML of one of the RSS feeds.

Figure 13.2. An RSS feed provided by the SafeFeeder application.





Presenting Microsoft Communication Foundation. Hands-on.
Microsoft Windows Communication Foundation: Hands-on
ISBN: 0672328771
EAN: 2147483647
Year: 2006
Pages: 132

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net