© 2004 Microsoft Corporation. All rights reserved.

Figure 1 PublishXML Web Method
[WebMethod]
public string PublishXML(XmlElement[] contentItems)  {
    string results = contentItems.Length.ToString() + " documents 
                     published successfully!";
    try  {
        foreach (XmlElement docXML in contentItems)  {
            DocumentXML doc = new DocumentXML(docXML); 
            doc.SaveXML();  // validate and save the xml via our 
                            // DocumentXML object 
        }
    }
    catch (Exception e) {
        results = "Error: " + e.Message;
   }
   return results;
}
Figure 2 DocumentXML Class
public class DocumentXML  {
    private string FileName;     // member variable for the xml file name
    private XmlElement Content;  // member variable for the content to be 
                                 // saved to disk
    
    public DocumentXML(XmlElement doc)  {
        // use the XmlTextReader to read the XmlElement argument to the 
        // constructor
        XmlTextReader reader = new 
                XmlTextReader(doc.OuterXml.ToString(),
                System.Xml.XmlNodeType.Element,null);
        // Position the reader to the file name by reading to the 
        // <FileName> tag
        while(reader.Read())  {
            if ("FileName" == reader.Name) break;
        }
        // if a <FileName> element was not found, the reader will be at 
        // EOF 
        if (reader.EOF)  {
            throw new Exception("The required 'FileName' element was not  
            found");
        }
        // if a <FileName> element was found, read the file name and save 
        // the file name and the raw xml to member variables
        else   {
            FileName = reader.ReadElementString("FileName");
            // check to see that we have a non-zero length file name 
            // (additionally, this function could check to see if the 
            // file already exists)
            if (FileName.Trim().Length > 0)   {
                Content = doc;
            }
            else  {
                throw new Exception("The required 'FileName' element was 
                empty.");
            }
            reader.Close();
        }
    }

    public void SaveXML()   {
        // create the output file and write the xml content to the file
        // NOTE: this will overwrite any existing file
        XmlTextWriter writer = new 
            XmlTextWriter(ConfigurationSettings.AppSettings.Get("ContentDirectory") 
            + FileName, System.Text.Encoding.Default);
        writer.WriteRaw(Content.OuterXml.ToString());
        // Close the writer and underlying file.     
        writer.Close(); 
    }
}
Figure 4 PublishXML.xml SOAP Envelope
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
        xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
        xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">

  <soap:Body>   (SOAP envelope body)

    <PublishXML xmlns="urn:webservices-paulsoftware-com-content"> (target 
    method)

      <contentItems>  (method argument name - one argument in this case)

        <Any> (first array element, enclosing the first XML document)
           <testDocument>
               <FileName>test1.xml</FileName>
               <info>
                 Any well formed xml can be included in the documents 
                     published via the PublishXML method.
                 <moreinfo>Each document must have a 'FileName' element, 
                     which specifies the file name that will be given to
                           the file created in the Content Directory
                   <moreinfo>Each 'document' must be enclosed in a single 
                   xml element 
                           (the 'testDocument' element, in this example).
                   </moreinfo>
                 </moreinfo>
                </info>
           </testDocument>
        </Any>

        <Any> (second array element, enclosing the second XML document)
           <anotherDocument>
               <FileName>test2.xml</FileName>
               <also>Multiple documents can be published via a single 
               call to PublishXML.</also>
               <note>Documents with the same FileName will be overwritten 
               in this example but the constructor of the DocumentXML 
               object could check for this condition.</note>
           </anotherDocument>
        </Any>

      </contentItems>

    </PublishXML>

  </soap:Body>
</soap:Envelope>
Figure 5 The PublishXML Method
public string Publish(Document[] contentItems)    {
    string results = contentItems.Length.ToString() + " documents 
                     published successfully!";
    try    {
        foreach (Document doc in contentItems)     {
          doc.Validate();   // Validate the content metadata
          doc.Save();       // Ask each content item to save itself 
                            // to disk
        }
    }
    catch (Exception e)    {
        results = "Error: " + e.Message;
   }
   return results;
}
Figure 6 The Document Class
public class Document
{
    //============================================================
    // Document constructor - empty
    //============================================================
    public Document()    {
    }
    // Document metadata that could be stored in a database or index
    // (document metadata is only provided as an example and is not 
    // processed)
    public string Author;
    public string Title;
    public string Abstract;
    public string[] Categories;

    // the file name (including application extension) used to save the 
    // binary file in the target Content Directory
    public string FileName;

    // the base64 encoded binary data
    public string Data;

    public void Validate() {
        if (FileName.Trim().Length == 0)   {
            throw new Exception ("FileName must be non-blank!);
        }
    }

    public void Save()    {
      int base64len = 0;
      byte[] base64 = new byte[1000];

      // create a string Xml fragment called <Content></Content> and 
      // place the base64 encoded data in the fragment
      string dataElement = "<Content>" + Data + "</Content>";

      // use the XmlTextReader to read the fragment, and use its 
      // ReadBase64 method to decode the data
      XmlTextReader reader = new XmlTextReader(dataElement,
          System.Xml.XmlNodeType.Element,null);

      // Position the reader to the base64 data by reading to the 
      // <Content> tag
      while(reader.Read())     {
         if ("Content" == reader.Name) break;
      }
      // we are positioned at the Base64 encoded content now, so:
      // 1) create the output file 
      // (NOTE: if you pass zero length content, a zero length file is 
      // created)
      FileStream fs = new 
          FileStream(ConfigurationSettings.AppSettings.Get("ContentDirectory") 
          + this.FileName, FileMode.CreateNew, FileAccess.Write);
      // 2) Create a binary writer, to write the decoded binary data to 
      // the file
      BinaryWriter w = new BinaryWriter(fs);  
      // 3) read the first chunk of the content
      base64len = reader.ReadBase64(base64,0,50);
      while (0 != base64len)      {
          w.Write(base64,0,base64len);
          base64len = reader.ReadBase64(base64,0,50);
      }
      // Close the Reader
      reader.Close();
      // Close the writer and underlying file.     
      w.Close(); 
    }
}
Figure 7 Publish.xml SOAP Envelope
<?xml version="1.0" encoding="utf-8"?> 
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
    xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body> 
    <Publish xmlns="urn:webservices-paulsoftware-com-content">   

      <contentItems>

        <Document> 
          <Author>Paula Paul</Author> 
          <Title>The Little Text Document</Title> 
          <Abstract>This is a test text document</Abstract> 
          <Categories> 
            <string>WebServices</string> 
            <string>Content</string> 
          </Categories> 
          <FileName>TestDoc.txt</FileName> 
          <Data>
VGhpcyBpcyBhIHNpbXBsZSB0ZXh0IGRvY3VtZW50Lg
          </Data> 
        </Document>  

        <Document> 
          <Author>Paula Paul</Author> 
          <Title>The Little Word Document</Title> 
          <Abstract>This is a test Word document</Abstract> 
          <Categories> 
            <string>Webservices</string> 
            <string>Binary Content</string>
          </Categories> 
          <FileName>TestDoc.doc</FileName> 
          <Data>
0M8R4KGxGuEAAAAAAAAAAAAAAAAAAAAAPgADAP7/CQAGAAAAAAAAAAAAAAABAAAAKgAAAAAAAAAA
•••
          </Data>    
        </Document> 
 
      </contentItems>

    </Publish>    
  </soap:Body> 
</soap:Envelope>  
Figure 8 MIMEMessage, SOAP Envelope Plus Content
MIME-Version: 1.0 
Content-Type: Multipart/Related; boundary=MIME_boundary; type=text/xml; 
        start="<soapenvelope.xml@paulsoftware.com>" 

--MIME_boundary
Content-Type: text/xml; charset=UTF-8 
Content-Transfer-Encoding: 8bit 
Content-ID: <soapenvelope.xml@paulsoftware.com>

<?xml version="1.0" encoding="utf-8"?> 
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body> 
    <Publish xmlns="urn:webservices-paulsoftware-com-content">      
      <contentItems> 
        <Document> 
          <Author>Paula Paul</Author> 
          <Title>The Little Text Document</Title> 
          <Abstract>This is a test text document</Abstract> 
          <Categories> 
            <string>WebServices</string> 
            <string>Content</string> 
          </Categories> 
          <FileName>MIMEtesttext.txt</FileName> 
          <Data href="cid:MIMEtesttext.txt@paulsoftware.com"></Data> 
        </Document> 
        <Document> 
          <Author>Paula Paul</Author> 
          <Title>The Little Word Document</Title> 
          <Abstract>This is a test Word document</Abstract> 
          <Categories> 
            <string>Webservices</string> 
            <string>Binary Content</string> 
          </Categories> 
          <FileName>MIMEtestdoc.doc</FileName> 
          <Data href="cid:MIMEtestdoc.doc@paulsoftware.com"></Data>    
        </Document>  
      </contentItems>
    </Publish>       
  </soap:Body>       
</soap:Envelope>
    
--MIME_boundary
Content-Type: text/plain; charset=us-ascii; name="MIMEtesttext.txt"
Content-Transfer-Encoding: Base64
Content-ID: <MIMEtesttext.txt@paulsoftware.com>

VGhpcyBpcyBhIHNpbXBsZSB0ZXh0IGRvY3VtZW50Lg

--MIME_boundary
Content-Type: application/msword; name="MIMEtestdoc.doc"
Content-Transfer-Encoding: Base64
Content-ID: <MIMEtestdoc.doc@paulsoftware.com>

0M8R4KGxGuEAAAAAAAAAAAAAAAAAAAAAPgADAP7/CQAGAAAAAAAAAAAAAAABAAAAKgAAAAAA
•••
--MIME_boundary
Figure 9 SOAPviaMIME SOAP Extension Class
public class PublishViaMIME : SoapExtension   {
  Stream oldStream;      // references to input and output streams
  Stream newStream;
  TextReader reader;     // reader and writer are used to copy streams
  TextWriter writer;
  string MIMEBoundary = "";
  string startContentID = "";
  string SOAPenvelope = "";
•••
  public override Stream ChainStream( Stream stream )    {
      //save a reference to the input stream and the return stream as 
      //local member variables.  The return stream will be modified in 
      //the ProcessMessage.BeforeDeserialize stage to replace the 
      // original input stream with a modified SOAP message stream.
      switch (stream.GetType().ToString())      {
          case "System.Web.HttpInputStream":    // we want to modify the 
                                                // input
              oldStream = stream;
              newStream = new MemoryStream();
              return newStream;
                                               // and avoid messing with 
                                               // the output
          case "System.Web.Services.Protocols.SoapExtensionStream": 
              return stream;
          default:
              throw new Exception ("Unknown Stream type in 
                                   ChainStream!");
    }
  }
  public override void ProcessMessage(SoapMessage message)     {
      switch (message.Stage)       {
      // Intercept the request before it is deserialized. Handle the
      // case where the SOAP envelope is part of a multipart MIME 
      // message. Otherwise, let the requst pass through normal 
      // serialization.
          case SoapMessageStage.BeforeDeserialize:     
              if (isMultiPartMIME(message.ContentType))   { 
                  //extract the SOAP envelope from the MIME Message
                  SOAPenvelope = getSOAPEnvelope(oldStream);
                  //extract the encoded content from the MIME message and
                  // insert the encoded content into the SOAP message
                  resolveContentRefs(oldStream); 
                  //replace the original (MIME) stream with the SOAP 
                  //message
                  replaceMessageStream(newStream);
                  //make sure we reset the request's content type, since 
                  //it is no longer in "Multipart/Related" form
                  message.ContentType = "text/xml";
              }
             //if not a multipart message, pass the original stream 
             //through
             else  {
                  reader = new StreamReader(oldStream);
                  writer = new StreamWriter(newStream);
                  writer.WriteLine(reader.ReadToEnd());
                  writer.Flush();
             }
             newStream.Position = 0;
             break;
          case SoapMessageStage.AfterDeserialize:
             break;
          case SoapMessageStage.BeforeSerialize:
             break;
          case SoapMessageStage.AfterSerialize:
             break;
          default:
             throw new Exception("invalid stage in ProcessMessage!");
      }
  }