Strengthening Your Attachments
Send binary data to your Web services using WS-Attachments
In the early days of .NET
Web services, using a
Web service to accept
binary data required converting
the data to something that
could be represented in XML. Surely
you remember those days – back
before December 2002. With the
release of Web Service
Enhancements (WSE) 1.0, followed
shortly by WSE 1.0 SP1 in March
2003, and now with WSE 2.0 on the
horizon, we can at last quickly and
easily send binary attachments via
Web services by utilizing WSAttachments.
The support of the proposed WSAttachments
standard in WSE is of
particular interest to any programmer
who has ever used a Web service
in a production environment.
You quickly realize how much you
rely on data that doesn't lend itself
to some sort of XML serialization.
You may want your Web service to
accept a word processing document,
an image, or any of a number
of binary data types. In the past, you
might have encoded this binary
data as Base64 and sent it over the
wire as XML, reconstituting the data
on the other side. While the process
works well, it is in many cases
clunky, and it is not useful in all situations.
The SOAP attachments may be
binary data, other SOAP messages,
or any other data type you wish to
send. WS-Attachments, as defined
in the WSE and as submitted as a
draft Web services standard, relies
on Direct Internet Message
Encapsulation (DIME). DIME is
used much like MIME
(Multipurpose Internet Mail
Extensions) to encapsulate a message,
and in fact uses the MIMEtypes
to indicate the type of attachment
it carries. An important thing
to remember is that DIME encapsulates
(as the name suggests) the
SOAP message and its attachments
– not the other way around.
For the purposes of this article,
we'll look at the practical application
of the WSE classes in order to
send a SOAP message with an
attachment to a Web service that in
turn responds with a different
attachment. The demonstration
code builds a Web service named
CodeWidgetServices with a Web
method Image2Jpg that accepts a
SOAP message with an image file
attachment. The Web service converts
the attached image file to a
JPEG image and returns the converted
file's name in the SOAP message
and the new JPEG as an attachment.
If you don't need cookbook
examples of implementing WSAttachments
in your own projects,
the quick checklist accompanying
this article will keep you headed in
the right direction as you go off on
your own. If you prefer to read the
article and code samples, the
checklist will let you know where
we are headed and is useful for
quick referral.
Building the DIME Web Service
To begin building a DIME-compatible
Web service, WSE 1.0 SP1
must be downloaded from
http://msdn.microsoft.com/webservices and installed. This will
install Microsoft.Web.Services.dll,
which contains the WSE classes and
the filters necessary for ASP.NET to
handle DIME. Once WSE 1.0 SP1 is
installed, you can begin a DIME-
enabled Web service project.
You'll also need to add a reference
to Microsoft.Web.Services.dll
to your Web service project. In addition,
you will need to check the
web.config file for the Web service,
ensuring that it has the
soapExtensionTypes elements (see
Listing 1). Ensure that if you are
manually adding the soapExtension
Types elements that the type attribute
has no breaks or extra spaces
in it despite how it may appear in
the listing. If you have line breaks in
the attribute value when you run
the service, the .asmx page will indicate
an invalid type.
Add aliases for the namespaces
you will use; otherwise you will
need to provide fully qualified
names in the code. The following
are the namespaces you will need to
reference in order to support DIME.
Using Microsoft.Web.Services
Using Microsoft.Web.Services.Dime
Setting Up a Web Method
Listing 2 shows the classic Web
service method format for Image2Jpg.
What may seem odd is that nowhere
does this method seem to indicate
that it relies on a SOAP attachment.
The method has the typical
(WebMethod) attribute and it accepts
a string and returns a string. Where's
the indicator for passed attachments?
At first glance, you would have no way
of knowing that this method relies on
an attachment. You could review the
code carefully, but this entry point
method may call other methods in the
class that deal with the attachment,
making it increasingly hard to recognize
that this Web method uses
attachments. And let's not even talk
about the possibility that one of the
called methods from the entry Web
method uses an inherited class with a
delegate that handles the attachments.
Good luck realizing an attachment
is used in that case!
A person looking at the classically
defined Web method would have to
investigate carefully to discover that
attachments are being utilized. Not
only is this currently acceptable – I
have failed to find any reference from
Microsoft on best practices to avoid
this sort of ambiguity. Later in this
article I will show you how to avoid
this ambiguity by using a recommended
way to indicate that attachments
are handled by a Web method.
What about the WSDL? Can we
look at the WSDL to discover that the
method uses DIME? Yes and no. Using
WSE 1.0 SP1 the WSDL is not automatically
modified to indicate DIME.
There is a proposed WSDL Extension
for SOAP in the DIME specification.
Nevertheless, WSE 1.0 SP1 does not
implement this. Oddly enough, the
SOAP Toolkit 3.0 does. Thus, if you
wish the WSDL to indicate that you
are using DIME, you will need to
modify the WSDL file manually. It's
not required that you do this if you are
using WSE on the client consuming
the Web service, but if you plan to use
the SOAP Toolkit 3.0 with the client it
needs the DIME information in the
WSDL. For the scope of this article, the
WSDL is not modified.
As a programmer I don't want to
have to look at the WSDL to see if a
Web method utilizes attachments, and
as we have learned, there is no guarantee
that doing so will tell you
whether or not the method utilizes
attachments. Furthermore, the SOAP
message could have an indefinite
number of attachments. You may
want to build code to ensure the message
sent only what was expected. Or
you may wish to have code validate
that the attachment is the proper data
type, e.g., not an image when the
method was expecting an XML document.
The best way to indicate the type
and number of attachments expected
– and that the method uses DIME – is
to create a custom attribute. Listing 3
shows the class definition for a custom
attribute named DimeCompliant
Attribute. Listing 4 is the complete
Web method code for Image2Jpg utilizing
the DimeCompliant attribute.
You will notice in the Web method
that I have not checked the message
against those attributes (because it is
outside the scope of this article), but
by utilizing reflection, you could validate
that the message meets the
expected message format. If nothing
else, the attribute is a strong flag to the
programmer that this Web method
uses attachments and what sort of
attachment it expects.
In the case of the sample code, it
indicates it expects one attachment of
type Bitmap (which is any of a number
of image types under .NET, e.g.,
JPEG, BMP, GIF, etc.). I hope that in
the future such attributes will be
required for members to receive DIME
information. It only makes sense that
a strongly typed language such as C#
should not allow a message to be sent
that can't be validated systematically
against an expected signature. What
do you need to do to set up a Web
method to handle SOAP attachments?
In short, you aren't required do anything
special to the method itself; you
can simply check the SOAP message
for attachments. However, I can
recommend better practices that
use custom attributes.
Building the Web Method
Listing 4 shows how the
RequestContext of the SOAP message
sent to the Web method can be
accessed and the attachments associated
with it handled. The first six
lines of code deal with accessing the
SOAP message's attachments and
streaming it into a Bitmap object.
All that is required to access the
attachments is to call HttpSoapCon
text.RequestContext.Attachments[ind
ex], which returns an object,
Microsoft.Web.Services. Dime.Dime
Attachment. The attachments can be
referenced by the index number of the
collection or by the DimeAttachment. id. In its most pure
form, a DimeAttch ment.id
should be a URI. In reality, it does
not have to be an URI but does
have to be unique to the collection
of attachments. You may prefer
to use a friendlier indicator
than a URI for the ID.
In the sample code, once the
sent image has been converted to
a JPEG we are now ready to create
a new DimeAttachment made up
of that JPEG image for the
response SOAP message. This is
done simply by creating a new
DimeAttachment instance indicating
the attachment's MIME
type, TypeFormatEnum, and the
stream that represents the actual
attachment data.
new DimeAttachment("image/jpeg", TypeFormatEnum.MediaType, myJpgStream)
After assigning the DimeAttachment
a friendly IDvalue, the attachment
is added to the ResponseContext.Attachments collection.
The Web method returns the new
filename or the error text if any of
a number of conditions occurs to
prevent the Web method from
returning a JPEG file. A DIME message
containing the SOAP message
and attachment will be returned to the client.
Setting Up a Client
In the sample code for the client
a simple form is built that presents
a Windows BMP file. When the user
clicks the "convert" button it sends
the BMP file to the Web service, gets
back the JPG file, and displays the
returned JPEG on the form. Not all
of the form coding is listed in the
sample code – only the two methods
that handle the outgoing and
incoming SOAP attachments and
DIME are shown. The client project
needs to reference the Microsoft.
Web.Services.dll as well as the Web
service you wish to utilize.
In the sample, the Web reference
is named CodeWidget. There is one
very important change on the client
side that must be made in order to
send and receive SOAP attachments
in conjunction with a Web service.
When using VS.NET and adding a
Web reference, a proxy class named
Reference.cs is created for the Web
service. This file can usually be
found under Project Folder/Web
References/web reference
name/Reference.cs, and will contain
a class that is a proxy for the
Web service referenced. This class
typically inherits from
System.Web.Services.Protocols.
SoapHttpClientProtocol. In order
to utilize DIME, the class needs to
be changed to inherit from
Microsoft.Web.Services.WebServicesClientProtocol.
Note: If you have installed WSE
1.0, Reference.cs likely already
contains an additional class that
inherits from WebServicesClientProtocol. This additional class will
have a class name ending in "Wse".
Use this class to create the Web
service instance in your code.
A very useful aspect of the
architecture of Web services built
with WSE is that a client can communicate
with the Web service
method designed for attachments
– without DIME – and the service
will not throw an exception. It will
see the SOAP message with zero
attachments because of how the
ASP.NET filters work with DIME. By
the time the actual Web method
sees the message, it is simply a
SOAP message like any other with
zero or more attachments.
Coding the Client
At this point coding the client
application is no different than coding
the Web service method. See Listing 5
for the sample code, which creates an
instance of the appropriate Web service
proxy class, creates a
DimeAttachment, adds the
DimeAttachment to the SOAP message,
calls the Web method, receives
the response, and processes the
returned DimeAttachment.
Using WSE 2.0
As I write this the WSE 2.0
Technology Preview is available.
It's unsupported and not authorized
for redistribution. Under the
tech preview, one of the major differences
you should be aware of is
that the Microsoft.Web.Services.
HttpSoapContext class has been
made obsolete and a new means
of accessing the SoapContext
exists. Look carefully at the
readme associated with WSE 2.0
to see the reasons for obsolescence,
and for how you will need
to tweak your code.
Conclusion
The implementation of WSAttachments
via WSE is not complicated,
but there are a few
tricky spots. In addition, there
are more changes to come under
WSE 2.0, and as I have pointed
out, there are issues that have not
yet been fully considered.
However, WS-Attachments via
WSE takes a large leap forward
for Web services and the ability to
easily send attachments. Despite
the various weak points and stillevolving
implementation, I recommend
implementing DIME
with SOAP in your Web services
now and further utilizing the
power as it evolves.
References
Altering the SOAP Message Using SOAP Extensions:
Click Here!
WSDL Extension for SOAP in DIME:
www.gotdotnet.com/team/xml_wsspecs/dime/WSDL-Extension-for-DIME.htm
Author Bio
Michael Ruminer is the president and founder of CodeWidget, a company specializing in .NET development and consulting. He has an extensive background in supply-chain operations and currently heads up building new "Widgets" in .NET for clients and
developers. He spends time both in Boston, MA, and Helena, MT.
michael@codewidget.com
Listing 1
<system.web>
...
<webServices>
<soapExtensionTypes>
<add
type=
"Microsoft.Web.Services.WebServicesExtension,
Microsoft.Web.Services,
Version=1.0.0.0,Culture=neutral,
PublicKeyToken=31bf3856ad364e35"
priority="1" group="0"/>
</soapExtensionTypes>
</webServices>
...
</system.web>
Listing 2
[WebMethod]
public string Image2Jpg (string ImageName)
{
...
}
Listing 3
[AttributeUsage(AttributeTargets.Method)]
public class DimeCompliant : System.Attribute
{
public readonly Type[] attachmentype;
public DimeCompliant(Type[] attachmenttype)
{
this.attachmentype = attachmentype;
}
Listing 4
[WebMethod]
[DimeCompliant(
new Type[] {typeof(System.Drawing.Bitmap)})]
public string Image2Jpg (string ImageName)
{
// Create objects for the Response and Request
// Soap Context this is
// slightly different if you are using WSE 2.0
SoapContext mySoapResponseContext =
HttpSoapContext.ResponseContext;
SoapContext mySoapRequestContext =
HttpSoapContext.RequestContext;
string retFileName;
if (mySoapRequestContext.Attachments.Count > 0)
{
//Create an IO.Stream from the Soap attachment
System.IO.Stream myBmpStream =
mySoapRequestContext.Attachments[0].Stream;
try
{
//note: Bitmap does not mean that the object
//is a windows "bitmap"
// it is a generic term for any sort of bitmapped
// image (could be wmf, tiff etc)
Bitmap bmp = new Bitmap(myBmpStream);
System.IO.MemoryStream myJpgStream =
new System.IO.MemoryStream();
//convert the image sent to a jpeg
bmp.Save(myJpgStream, ImageFormat.Jpeg);
//Create the dime attachment from the
// new IO stream
DimeAttachment dimeImage =
new DimeAttachment("image/jpeg",
TypeFormatEnum.MediaType,
myJpgStream );
dimeImage.Id = ImageName + ".jpg";
//Attach the new image to the
// outgoing soap message
mySoapResponseContext.Attachments.Add(dimeImage);
retFileName = dimeImage.Id;
}
catch (System.ArgumentException arge)
{
retFileName =
"Attachment not a proper image format";
}
}
else
{
retFileName = "No attachment to convert";
}
return retFileName;
}
Listing 5
private void button1_Click(object sender,
System.EventArgs e)
{
//Create the WSE Web Service proxy object
CodeWidget.CodeWidgetServicesWse cws =
new CodeWidget.CodeWidgetServicesWse();
filename = @"c:\temp\disc.bmp";
//create filestream that will be used in the
//DimeAttachment Constructor
System.IO.FileStream bmpfs =
new System.IO.FileStream(filename,
System.IO.FileMode.Open);
DimeAttachment dimeImage =
new DimeAttachment("image/bmp",
TypeFormatEnum.MediaType,
bmpfs);
cws.RequestSoapContext.Attachments.Add(dimeImage);
string returnedstring cws.Image2Jpg(Path.GetFileNameWithout
Extension(bmpfs.Name));
bmpfs.Close();
UpdateImage(cws, returnedstring);
}
private void UpdateImage (
WebServicesClientProtocol wscp,
string returnedstring)
{
label2.Text = returnedstring;
if (wscp.ResponseSoapContext.Attachments.Count
< 1 )
{
return;
}
try
{
pictureBox2.Image = new
System.Drawing.Bitmap(
wscp.ResponseSoapContext.Attachments[0].Stream);
}
catch (ArgumentException arge)
{
label2.Text = "Unsupported Image returned";
}
}
All Rights Reserved
Copyright © 2004 SYS-CON Media, Inc.
E-mail:
info@sys-con.com