HomeDigital EditionSearch Dotnet Cd
ASP.NET C# Certification Exams The CLI Data Access Editorials Extending .NET Fundamentals Interoperability Interviews Migrate Mobile .NET Mono .NET Interface Object-Oriented Programming Open Source Optimization Product/Book Reviews Security Source Code UML Visual Studio .NET

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