XtremeData
LANGUAGES: C#
TECHNOLOGIES: WSE | SOAP | DIME | GDI+
Attach Docs to SOAP Messages
Send binary data from Web services with the WSE.
By Dan Wahlin
Web services are constantly evolving to support more and more advanced requirements. Fortunately, new tools are emerging to help simplify the development of Web services and make them more efficient in the process. In this article, I'll introduce you to one new and exciting Web service technology relating to a specification referred to as WS-Attachments.
Consider a Web service that returns a binary image, such as a JPEG. Because the returned data is binary, it doesn't make much sense to "serialize" it into a SOAP message. The WS-Attachments specification was created for just that reason. Supported by Microsoft's Web Service Enhancements (WSE), the WS-Attachments specification instead allows a SOAP message to reference an image (or other document). This reduces the processor's overhead because it doesn't have to convert binary data to encoded bytes, thus minimizing the size of the SOAP messages. (For more on WSE, see the sidebar, "Web Service Enhancements.")
In a nutshell, the WS-Attachments specification defines how you can "attach" documents and files to a SOAP message as opposed to embedding them directly within the body of one. This is possible by encapsulating the SOAP message and document using a technology named Direct Internet Message Encapsulation (DIME).
Build a Charting Web Service
To see WS-Attachments and the WSE in action, let's take a look at how to create a Web service capable of generating on-the-fly bar and line charts. This service accepts an XML document containing data points to add to the chart and returns a png image as a SOAP attachment (the service also could return other image types). Figure 1 shows an example of a bar chart returned from the charting Web service.
Figure 1. The charting Web service parses XML data and is used along
with GDI+ classes in .NET to create a bar chart.
The XML document that was sent to the Web service and used to create the bar chart in Figure 1 is shown in Figure 2.You'll notice by looking through the different elements and attributes that the document marks up point data the Web service should graph.
width="500"
graphTitle="Golf Scores">
Figure 2. This simple XML document defines the size of the image, what type of chart should be generated, and the data to be graphed. The client sends the XML in a SOAP message to the charting Web service.
The charting Web service relies on a custom C# class named ChartGenerator to parse the XML shown in Figure 2. ChartGenerator has several different methods that handle extracting data from the XML document and drawing lines, text, and individual bars. Figure 3 shows ChartGenerator's only public method, GenerateChart (you can download the entire ChartGenerator class).
public Stream GenerateChart() {
//Get height and width of chart
XmlElement root = (XmlElement)this.DataSource;
_ChartSize =
new SizeF(float.Parse(root.GetAttribute("width")),
float.Parse(root.GetAttribute("height")));
int totalPoints = 0;
//Find maximum value of XML chart data
this._MaxValue = GetMaxValue(out totalPoints);
//Find number of points in XML chart data
this._TotalPoints = totalPoints;
//Create a new Bitmap object
Bitmap b =
new Bitmap((int)_ChartSize.Width,
(int)_ChartSize.Height,
PixelFormat.Format32bppArgb);
//Create a graphics drawing surface
_Graphics = Graphics.FromImage(b);
//Set background color to white
_Graphics.Clear(Color.White);
_Graphics.SmoothingMode = SmoothingMode.AntiAlias;
_Graphics.TextRenderingHint =
TextRenderingHint.AntiAlias;
//Delegate responsibility for drawing lines,
//text, and bars to the class's private methods
DrawLines();
DrawText();
DrawBars();
//Return a stream containing the image data
//to the caller of the method
MemoryStream s = new MemoryStream();
b.Save(s,ImageFormat.Png);
return s;
}
Figure 3. The ChartGenerator class has several methods that draw the chart image. The Web service calls the GenerateChart() method to handle the drawing of the chart.
GenerateChart creates the initial Bitmap object that will be drawn upon by reading height and width data from the XML document shown earlier. It also discovers the maximum value of the XML data points, determines the total number of data points, and creates a Graphics object used to draw upon the Bitmap. Once these activities are complete, lines, text, and bars are drawn by calling three private methods: DrawLines, DrawText, and DrawBars.
After the image is complete, the GenerateChart method returns it as a MemoryStream object. The image could be saved to disk instead of being returned as a stream, but by using a stream, the application will perform more efficiently because the file system won't have to be touched. Currently, the GenerateChart method returns a png image format; the code easily can be modified, however, to return other formats such as JPEG or BMP.
Add DIME to Web Methods
Now that you're familiar with the ChartGenerator class and
its GenerateChart method, let's see how to call it and attach the returned
image stream to a SOAP message using the WSE. The first task is to modify the
web.config file. To integrate the WSE classes into the SOAP processing
pipeline, you must add the configuration code in Figure 4 to web.config,
between the
priority="1"
group="0"/> Figure 4. Although installing the WSE provides you
with access to many different extension classes, different types of
configuration entries must be added into the web.config file for these classes
to be used within Web services. In the case of WS-Attachments functionality,
the XML configuration code shown here must be added within the
After modifying web.config, the Microsoft.Web.Services and
Microsoft.Web.Services.Dime namespaces must be imported into the chart Web
service to use the WS-Attachments and DIME classes. A class named
DimeAttachment contains DIME-specific functionality. You can create a
DimeAttachment instance by using one of several overloaded constructors (see
Figure 5). public DimeAttachment(); public DimeAttachment(System.String, TypeFormatEnum,
System.IO.Stream); public DimeAttachment(System.String, TypeFormatEnum,
System.String); public DimeAttachment(System.String, System.String, TypeFormatEnum,
System.IO.Stream); public DimeAttachment(System.String, System.String,
TypeFormatEnum, System.String); Figure 5. The DimeAttachment class has several
overloaded constructors you can use to create an instance of the class. The
path to the document to attach a stream containing the document data can be
passed to the constructor. See the WSE documentation for more details about the
constructor parameters. Each constructor allows you to identify the type of
payload (i.e., "image/jpeg" or "image/png") being loaded as an attachment as
well as its format. An enumeration named TypeFormatEnum represents the format,
and it can have a value of AbsoluteUri, MediaType, None, Unchanged, or Unknown.
This article's charting Web service uses a TypeFormatEnum value of MediaType.
Aside from these parameters, you can load the source document to be attached by
passing in the path to the document or the stream that represents the document. Associating a DimeAttachment class with a SOAP message
requires access to the current SOAP context. Because the charting Web service
sends an attachment along with the SOAP Response message, the Response context
must be accessed. You do this by calling the HttpSoapContext object's
ResponseContext property (see Figure 6). [WebMethod] public bool GenerateChartImage(XmlNode data) { bool status = true; try { //Create instance of
charting class ChartGenerator gen =
new ChartGenerator(); //Assign XML data
points to DataSource property gen.DataSource = data; //Generate the chart
image Stream s =
gen.GenerateChart(); //Access the SOAP
Response context SoapContext resp =
HttpSoapContext.ResponseContext; //Attach the chart
image (DIME encapsulation) resp.Attachments.Add( new DimeAttachment("image/png", TypeFormatEnum.MediaType,s)); } catch (Exception exp) { status = false; } return status; } Figure 6. The GenerateChartImage() Web Method creates
an instance of the ChartGenerator class, assigning the chart's data source,
then calls its GenerateChart() method. The GenerateChartImage Web Method calls the ChartGenerator
object and adds the returned stream into a DimeAttachment object. Then, this
attachment is added to the current Response SOAP context's Attachments collection.
Note that, with only a few lines of code, you can add a document as a SOAP
attachment. Very nice! Access SOAP Attachments The client of the charting Web service must perform three
tasks to generate and return the chart. First, it must create an XML document
containing chart data points (refer to the XML document shown in Figure 2).
Next, it must call the Web service's GenerateChartImage Web Method and pass the
XML document as a parameter. This call is made through a Web service proxy
object. Note that the client proxy class must inherit from the
Microsoft.Web.Services.WebServicesClientProtocol class rather than the normal
System.Web.Services.Protocols.SoapHttpClientProtocol class to use the WSE
classes. This change requires you to edit the proxy code manually. Finally, the
client must access the Response SOAP context, extract the image attachment, and
either save it to the file system or write it to the Response stream. Figure 7
demonstrates how to perform these steps. Several comments have been added to
help explain what the code is doing at each step. Bitmap b = null; XmlDocument xmlDoc =
new XmlDocument(); //1. XML loaded from text area. It could also be loaded //from a file or created dynamically. xmlDoc.LoadXml(this.txtChartData.Text); //2. Call the Web Service through the proxy ChartService.ChartServiceWebServiceProxy.ChartService proxy = new
ChartService.ChartServiceWebServiceProxy. ChartService(); proxy.GenerateChartImage(xmlDoc); //3. Ensure that an attachment was received and save it //either to a file or to the Response output stream if (proxy.ResponseSoapContext.Attachments.Count > 0) { //Load chart image
attachment into Bitmap b = new Bitmap( proxy.ResponseSoapContext.Attachments[0].Stream); MemoryStream s = new
MemoryStream(); //Save to memory
stream...necessary for png images b.Save(s,ImageFormat.Png); //Set content type Response.ContentType =
"image/png"; //Save stream data to
the Response stream s.WriteTo(Response.OutputStream); } Figure 7. The Web service's client does not need to
install any charting software to generate chart images. The client must have
the WSE classes installed, however, to use WS-Attachments/DIME functionality. Figures 8 and 9 show additional types of chart images you
can generate by passing different XML data to the charting Web service. The WSE makes implementing the WS-Attachments
specification extremely straightforward. As a result, different types of
documents - from images to PDFs to Word documents - can be exchanged between
entities in a much more efficient manner compared to "serializing" the
documents into the SOAP message. This technology opens up a whole new world for
Web services because clients with WS-Attachments capabilities can hit services
that return a wide variety of complex data without installing specialized
software. You can run a live example of the charting Web service at
the following URL on the XML for ASP.NET Developers Web site: http://www.XMLforASP.NET/codeSection.aspx?csID=96. The sample code referenced in
this article is available for download. Dan Wahlin (a Microsoft Most Valuable Professional in
ASP.NET) is the president of Wahlin Consulting and founded the XML for ASP.NET
Developers Web site (http://www.XMLforASP.NET), which
focuses on using XML and Web Services in Microsoft's .NET platform. He also is
a corporate trainer and speaker, and he teaches XML and ASP.NET training
courses around the United States. Dan co-authored Professional Windows DNA (Wrox) and ASP.NET Tips, Tutorials
& Code (Sams), and he authored XML for ASP.NET Developers (Sams). E-mail
Dan at [email protected]. You can think of DIME as a wrapper around a SOAP message
and an associated document or file. For more information, you can find an article on the
subject at http://msdn.microsoft.com/webservices/default.aspx?pull=/library/en-us/dnwebsrv/html/DIMEWSAttch.asp.
You also can read the full WS-Attachments specification at http://msdn.microsoft.com/webservices/understanding/gxa/default.aspx?pull=/library/en-us/dnglobspec/html/wsattachmentsindex.asp. Web Services started out as a very simple concept:
Exchange data between two or more entities, using XML messages. With the
release of development platforms such as Microsoft's .NET Framework, this
initial concept has become useful in many different scenarios. Because of the growth of the Web Services technology and
its rapid proliferation, though, it has been in need of new and improved
features to handle a variety of tasks such as securing messages and
authenticating Web service callers. To accommodate this need, Microsoft has
released Web Service Enhancements (WSE), which lets you integrate such features
into Web services with only a minimal investment of time and effort. In this article, I've dealt only with one specific area of
WSE - the new WS-Attachments specification - but Web Services has experienced
growth pains in other areas, as well. If you're interested in learning more
about other WSE features that help address these growth pains, such as
authenticating Web service callers and encrypting SOAP messages, see Practice
Safe Web Services and Protect
Your Info. Tell us what you think! Please send any comments about
this article to [email protected].
Please include the article title and author.
Figure 8. This image shows how to overlay a line chart on a bar chart.
You accomplish this by setting the barChart and lineChart attribute values to
True in the XML document that is sent to the Web service.
Figure 9. You can generate line charts using the charting Web service by
setting the barChart attribute in the source XML document to False and the
lineChart attribute to True.Web Service Enhancements