Handling Attachments

Handling Attachments

One of the most important features Borland added to Delphi 7 is full support for SOAP attachments. Attachments in SOAP allow you to send data other than XML text, such as binary files or images. In Delphi, attachments are managed through streams. You can read or indicate the type of attachment encoding, but the transformation of a raw stream of bytes into and from a given encoding is up to your code. This process isn't too complex, though, if you consider that Indy includes a number of encoding components.

As an example of how to use attachments, I've written a program that forwards the binary content of a ClientDataSet (also hosts images) or one of the images alone. The server has the following interface:

type
  ISoapFish = interface(IInvokable)
  ['{4E4C57BF-4AC9-41C2-BB2A-64BCE470D450}']
    function GetCds: TSoapAttachment; stdcall;
    function GetImage(fishName: string): TSoapAttachment; stdcall;
  end;

The implementation of the GetCds method uses a ClientDataSet that refers to the classic BIOLIFE table, creates a memory stream, copies the data to it, and then attaches the stream to the TSoapAttachment result:

function TSoapFish.GetCds: TSoapAttachment; stdcall;
var
  memStr: TMemoryStream;
begin
  Result := TSoapAttachment.Create;
  memStr := TMemoryStream.Create;
  WebModule2.cdsFish.SaveToStream(MemStr); // binary
  Result.SetSourceStream (memStr, soReference);
end;

On the client side, I prepared a form with a ClientDataSet component connected to a DBGrid and a DBImage. All you have to do is grab the SOAP attachment, save it to a temporarily in-memory stream, and then copy the data from the memory stream to the local ClientDataSet:

procedure TForm1.btnGetCdsClick(Sender: TObject);
var
  sAtt: TSoapAttachment;
  memStr: TMemoryStream;
begin
  nRead := 0;
  sAtt := (HttpRio1 as ISoapFish).GetCds;
  try
    memStr := TMemoryStream.Create;
    try
      sAtt.SaveToStream(memStr);
      memStr.Position := 0;
      ClientDataSet1.LoadFromStream(MemStr);
    finally
      memStr.Free;
    end;
  finally
    DeleteFile (sAtt.CacheFile);
    sAtt.Free;
  end;
end;
Warning 

By default, SOAP attachments received by a client are saved to a temporary file, referenced by the CacheFile property of the TSOAPAttachment object. If you don't delete this file it will remain in a folder that hosts temporary files.

This code produces the same visual effect as a client application loading a local file into a ClientDataSet, as you can see in Figure 23.7. In this SOAP client I used an HTTPRIO component explicitly to be able to monitor the incoming data (which will possibly be very large and slow); for this reason, I set a global nRead variable to zero before invoking the remote method. In the OnReceivingData event of the HTTPRIO object's HTTPWebNode property, you add the data received to the nRead variable. The Read and Total parameters passed to the event refer to the specific block of data sent over a socket, so they are almost useless by themselves to monitor progress:

procedure TForm1.HTTPRIO1HTTPWebNode1ReceivingData(
  Read, Total: Integer);
begin
  Inc (nRead, Read);
  StatusBar1.SimpleText := IntToStr (nRead);
  Application.ProcessMessages;
end;
Click To expand Figure 23.7: The FishClient example receives a binary ClientDataSet within a SOAP attachment.


Part I: Foundations