Web Services has led to so many new, emerging standards, it's difficult to track them all. With this in mind, I decided to more closely examine the DIME specification, and specifically the tools Microsoft offers to handle Direct Internet Message Encapsulation (DIME) within .NET.
When we hear "DIME," we need only to think about attachments. The client and the server are sending messages back and forth, and documents can be optionally attached to these messages. For example, an organization may have a Web Service that enables you to send it postal zip code and it will return a series of maps about that location. DIME could provide a flexible method for transmitting the binary map data.
Currently binary data can be sent and received within a normal Web Service in .NET Framework simply by using the byte[] data type on messages. The framework will automatically base64 (http://www.topxml.com/xml/articles/binary/) encode and decode this data over the wire. Here is an example of this on the server:
[WebService(Namespace="http://rob.santra.com/webservices/public/images/",
Description="Demonstration of using Base64 encoding, in a Web Service using the
.NET Framework.")]
public class Images: System.Web.Services.WebService {
[WebMethod(Description="Get an Image using Base64 Encoding")]
public byte[] GetImage() {
return GetImageAsBytes(); //simplified example
}
The GetImage method has a return type of byte[]. It takes the image, say a GIF or JPEG, and converts it (packs it using base64 encoding) into a format that we can send over Web Services within eXtensible Markup Language (XML). This takes the entire data into a single buffer while a message is created for the client. With DIME, however, chunks of data can be sent down the wire instead of buffering the entire message. This has obvious advantages because the server can quickly respond with the message and allow for the client to start processing the binary message while the server is still sending data. For example, there may be a large database query. As the server produces results, it can just keep sending it to the client, and the client can receive and take action based on that.
Lastly, at least skim over the excellent introduction to the DIME specification at http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnservice/html/service01152002.asp. Just get an overview of what DIME is, pay attention to the fact that it is a specification for sending and receiving document attachments, understand how it differs from Multipurpose Internet Mail Extensions (MIME), and recognize the size limitations of this protocol. Don't bother memorizing the exact details, like the DIME headers, or what the bit-by-bit message headers would look like, but gain an overview of how and why this specification was created.
This article focuses on the implementation of a simple application that uses DIME technology. I create a tool similar to the File Transfer Protocol (FTP) (since most of us understand what FTP does), but apply DIME to the actual transferring of the documents.
The application allows for an administrator to publish documents and folders. It has a Web Services interface that uses Simple Object Access Protocol (SOAP) headers for authentication and DIME when sending and receiving the documents. For this demonstration I used SQL Server for storing the user credentials and for keeping track of which folders are exposed on our Web Service.
Users provide the application with their credentials (a username and password) and an end point for the Web Service. Our client code will connect to this Web Service, and if the credentials are acceptable (valid username and password), the Web Service will return a list of the available folders or an error.
Next, the client can browse the folders and get a list of files in each. Once they have chosen a file, they can download it, edit it, and then optionally upload it back to the server.
As you can see this is very similar to FTP. It is not my intention to replace FTP, but to merely demonstrate how you would create an application using DIME and Web Services in a fashion that you are familiar with. Most can relate to the functionality of the FTP process.
The Server
Authentication
For this application, SOAP headers send and receive authentication information. Here is a look at our header class.
using System;
namespace IDSSServer {
public class AuthHeader : System.Web.Services.Protocols.SoapHeader {
protected string username;
protected string password;
protected string guid;
protected System.DateTime sessionStarted;
protected IDSSServer.User user;
public AuthHeader(){}
public AuthHeader(string Username, string Password) {
username=Username;
password=Password;
user = new IDSSServer.User(Username, Password);
}
public AuthHeader(string Guid) {
guid=Guid;
}
public string Username{get{return username;}set{username=value;}}
public string Password{get{return password;}set{password=value;}}
public string Guid{get{return guid;}set{guid=guid;}}
public System.DateTime SessionStarted{get{return
sessionStarted;}set{sessionStarted=sessionStarted;}}
}
}
Recognize how this class creates an instance of the IDSSServer.User() class. This user class is where the actual authentication occurs. This is an example of how to tie this system into an existing user authentication infrastructure in an existing application. The user class follows:
using System;
namespace IDSSServer {
[System.Serializable()]
public class User {
protected string username="";
protected string password="";
protected string guid="";
protected System.DateTime sessionStarted=System.DateTime.MinValue;
public User(string Username, string Password) {
username=Username;
password=Password;
}
public User(string Username, string Password, string Guid) {
username=Username;
password=Password;
guid=Guid;
}
public User(string Guid) {
guid=Guid;
}
public bool Login() {
IDSSServer.DAL dal = new IDSSServer.DAL();
string[] userdata = dal.GetUserData(username, password, guid);
if(userdata!=null) {
this.username=Convert.ToString(userdata[0]);
this.password=Convert.ToString(userdata[1]);
this.guid=Convert.ToString(userdata[2]);
this.sessionStarted=Convert.ToDateTime(userdata[3]);
return true;
} else {
this.username=null;
this.password=null;
this.guid=null;
this.sessionStarted=System.DateTime.MinValue;
return false;
}
}
public string Username{get{return username;}set{if(value!=null)username=value;}}
public string Password{get{return password;}set{if(value!=null)password=value;}}
public string Guid{get{return guid;}set{if(value!=null)guid=value;}}
public System.DateTime SessionStarted{get{return
sessionStarted;}set{if(value!=System.DateTime.MinValue)sessionStarted=value;}}
}
}
Here you can see that the Login() method creates and calls an instance of our data access layer (DAL), the IDSSServer.DAL() class. This DAL is responsible for performing the interaction with our data store. It's useful to create a single entry point to our data store, whether it's a Microsoft Access database, XML document, SQL Server database, or even the file system. In this case, the DAL will provide an interface into SQL Server and our file system. This offers a single point of change if you ever need to modify the location of your data.
For example, I could have created this application by simply using an XML data store for the user data and folder listings, and then upgraded the entire application so it used SQL Server exclusively. In this case I would only need to change this DAL class, and nothing else in the application would be effected or require change. This will prove important when we need to grow our system to meet the demands of our client base. Here is the user authentication portion of our DAL:
It is simply calling out to SQL Server to ensure that the username and password or Globally Unique IDentifier (GUID) are valid. If the GUID is not valid, but the username and password are, it will assign a new GUID to be used. Otherwise it will not produce a valid GUID, and the login will be invalid.
The next step is the stored procedure "PerformLogin." Since this is fairly basic and not specifically related to this article, it will not be explained.
Folder Listing
Once we have a valid user, we need to get a list of the folders the user may access. A database is used to list the folder and control access to each folder. The stored procedure "GetFolders" and look at the two tables, "Folders" and "UserFolders." What I have done here is self-explanatory.
The ability to generate a list of folders must now be implemented by our Web Service. In the DAL, our direct connection to the database, is a method that will query the database, using the "GetFolders" stored procedure, for a list of folders:
Within the while loop we are creating an instance of a Folder object. I created this class to handle the description of exactly what a folder is, which makes it easier for the client and the server to deal with a folder when they are communicating with each other. Our method above will gather a list of these folder objects and return them back to our Web Service. Here is the Folder class:
using System;
namespace IDSSServer {
[System.Serializable()]
public class Folder {
protected string name;
protected string fullpath;
protected bool write;
public Folder() {
}
public Folder(string Path) {
}
public string Name{get{return name;}set{name=value;}}
public string Fullpath{get{return fullpath;}set{fullpath=value;}}
public bool Write{get{return write;}set{write=value;}}
}
}
We have a collection of folders, but let's back up and look at the actual Web Service. The Web Service simply exposes a method that allows the user to use the authentication features described above and to retrieve a list of the folders. Here is the relevant portion of the service:
public IDSSServer.AuthHeader authHeader;
[WebMethod]
[System.Web.Services.Protocols.SoapHeader("authHeader",
Direction=System.Web.Services.Protocols.SoapHeaderDirection.InOut,Required=true)]
public IDSSServer.Folder[] GetFolderList() {
IDSSServer.Folder[] folders=null;
IDSSServer.User user = new IDSSServer.User(authHeader.Username,
authHeader.Password, authHeader.Guid);
if(user.Login()) {
//valid login
IDSSServer.DAL dal = new IDSSServer.DAL();
System.Collections.ArrayList flds = dal.GetUserFolders(user);
folders = new IDSSServer.Folder[flds.Count];
int x =0;
foreach(IDSSServer.Folder folder in flds) {
folders[x]=folder;
x++;
}
} else {
throw new System.Web.Services.Protocols.SoapException("Invalid
Login", new System.Xml.XmlQualifiedName("AuthHeader"));
}
return folders;
}
We are implementing our authentication header class by first creating a local instance of the "AuthHeader()" class. Then we are adding the "SoapHeader()" attribute on our GetFolderList() member, with the first parameter being the name of our local instance of our class. This process should be familiar to you, and if not, review http://www.aspalliance.com/nothingmn/default.aspx?aid=123. Now that we have the header class, we need to use it, and that's what we are doing here:
IDSSServer.User user = new IDSSServer.User(authHeader.Username,
authHeader.Password, authHeader.Guid);
if(user.Login()) {
The User class is loaded with the relevant authentication information received from the Web Service client. The user attempts to log in based on that data. If this fails, we gracefully throw back the SoapException, which the client is in charge of dealing with. The SoapException is the exception that is thrown when an XML Web service method is called over SOAP and an exception occurs. If the authentication details are valid, we can then connect to our DAL, grab our folders, and send the folders back out to the calling Web Service client. In the future, recognize how each request to the server will go through the same few basic lines of authentication, so I will not cover this again.
Retrieving a List of Files
Once the end user has a list of folders, a list of the files within each folder can be requested. In the Web Service and the DAL we need to provide the necessary methods to perform these operations. In our Web Service there is:
[WebMethod]
[System.Web.Services.Protocols.SoapHeader("authHeader",
Direction=System.Web.Services.Protocols.SoapHeaderDirection.InOut,Required=true)]
public string[] GetFileList(string fullpath) {
IDSSServer.User user = new IDSSServer.User(authHeader.Username,
authHeader.Password, authHeader.Guid);
if(user.Login()) {
//valid login
return DAL.GetFiles(user, fullpath);
} else {
throw new System.Web.Services.Protocols.SoapException("Invalid
Login", new System.Xml.XmlQualifiedName("AuthHeader"));
}
}
This method allows the user to request a list of files based on a given path. Within the application, the path is provided. In the DAL, this is simply calling the GetFiles() method within the System.IO.Directory class.
The user needs to be able to grab the file from the server and download it to the client. This is where DIME affects our application. Our method makes a call to the DAL to request the Microsoft.Web.Services.Dime.DimeAttachment that will represent the requested file.
[WebMethod]
[System.Web.Services.Protocols.SoapHeader("authHeader",
Direction=System.Web.Services.Protocols.SoapHeaderDirection.InOut,Required=true)]
public void GetFile(string fullpath) {
IDSSServer.User user = new IDSSServer.User(authHeader.Username,
authHeader.Password, authHeader.Guid);
if(user.Login()) {
//valid login
Microsoft.Web.Services.Dime.DimeAttachment attach =
DAL.GetFile(user, fullpath);
Microsoft.Web.Services.HttpSoapContext.ResponseContext.Attachments.Add(att
ach);
} else {
throw new System.Web.Services.Protocols.SoapException("Invalid
Login", new System.Xml.XmlQualifiedName("AuthHeader"));
}
}
Within the DAL, we can take a file stream and plug that directly into our Microsoft.Web.Service.Dime.DimeAttachment object. Using DIME on the server is an extremely easy process.
If necessary, we could also permit the user to request more than one file, and all we would have to do is load up the DimeAttachment and add it to our Attachments collection.
Uploading a File
On the server side of this application, we will want to take an attachment sent by the client and save it back to disk. The member that we have for this also uses the DimeAttachment class to communicate with and accept the document. In our implementation, the Web Service code simply takes the file and calls upon our DAL to save the attachment to disk:
[WebMethod]
[System.Web.Services.Protocols.SoapHeader("authHeader",
Direction=System.Web.Services.Protocols.SoapHeaderDirection.InOut,Required=true)]
public bool SaveFile(string FullPath) {
IDSSServer.User user = new IDSSServer.User(authHeader.Username,
authHeader.Password, authHeader.Guid);
if(user.Login()) {
//valid login
return IDSSServer.DAL.SaveFile(FullPath,
Microsoft.Web.Services.HttpSoapContext.RequestContext.Attachments[0].Stream);
} else {
throw new System.Web.Services.Protocols.SoapException("Invalid
Login", new System.Xml.XmlQualifiedName("AuthHeader"));
}
}
Given the complexity of Web Services and DIME, appreciate how much work Microsoft put into the Web Services Enhancements (WSE) technology in order to simplify things for us. In fact, most of us could easily implement this with little knowledge of the underlying protocols and specifications. This is typical of the .NET Framework as a whole.
ASP.NET and WSE
Add a new section within the Web.config file, under the system.web section. This registers a new extension on the server, which the Microsoft.Web.Services implementation requires in order to work properly within the ASP.NET environment.
To this point, the article has addressed the server implementation, and it'll now examine the client. We will need to generate a proxy client for our Web Service. This can be done in the manner described previously. Either use the Web Services Description Language (WSDL) wsdl.exe command-line utility or select the Web Reference menu item within VS.NET. Also, take a closer look at the WSE Settings tool. The WSE Settings tool is used to manage the WSE application settings from within VS.NET. Download it at http://msdn.microsoft.com/downloads/default.asp?url=/downloads/sample.asp?url=/MSDN-FILES/027/002/108/msdncompositedoc.xml.
I suggest downloading the source code and samples provided in the download package now. Run the application, and play with it on your own server. This will clarify things for the rest of this article.
The first thing we must do on the client is allow the user to provide his/her username and password credentials. A simple Connect form is provided for this purpose.
Let's drill down and see what the "Connect..." button is doing.
First, recognize that it validates the local path. This local path is a temporary cache folder used when files and folders are downloaded off our service. Next it creates an IDSSClientLibrary.Connection() object. This object's primary task is to perform all of the work with our Web Service via the proxy. It allows us to have a single interface to handle all of the communication with the Web Service. Lastly it attempts to call the Connect() method in our Connection() object. This method is as follows:
This is simply creating an instance of our proxy class for the Web Service. It then ensures that the header authentication details are added to the request, and it clears any existing attachments that are on the Request or Response SOAP context. This is done because we will be reusing the same instance of the proxy object itself, and we want to have a clear Attachment list before using it each time.
The most important line of code is where we are calling the GetFolderList(). It sends authentication information and retrieves the folder list simultaneously. If the Web Service throws an authentication error, no folders will be returned, and the user will be denied access to the folder listing and the service. If the authentication information is valid, the user will have a collection of folders to work with.
Take time now to review the rest of the implementation of the Connection.cs file (and class). You will see that it is there in order to simplify and centralize the connection implementation using the proxy with the Web Service. It also handles all the necessary work with the DimeAttachments. I will cover these two methods (SaveFile, and GetFile) in more detail later.
Viewing and Navigating Folders
Once we have established a good connection with the service via the GetFolderList() method, we will also have a collection of available folders for this user. In the Browse.cs file source we are providing the end user with the ability to use this folder listing to load up a tree control on the form. Then, based on their actions (clicks within the tree), we are calling the service to get file listings for each folder, and finally downloading the specific file to the client machine's temporary cache folder. The Browse interface is shown below:
In conclusion, I want to quickly cover how we interact with our proxy to download and upload our documents using DIME.
Downloading Files
In the Browse form, when we click the download arrow "<-", we are initiating a Web Service request to download the chosen file. Here is the relevant section of code for this that I pulled out of our Connection.cs file:
The first thing we do is call the GetFile() method on the proxy, which makes the call to the Web Service and pulls down the document specified via the DIME attachments. We are reading the first Attachment (0) off of the ResponseSoapContext. Next we convert that document to a byte[] buffer and save it to disk in our cache.
Uploading Files
The last piece of the puzzle has to do with uploading documents via DIME to our Web Service. This is done in almost the same way as how we did it on the server. First you create a stream to hold the content of the attachment ("stream"), and then you create an actual Microsoft.Web.Services.Dime.DimeAttachment object from the stream. Finally, add that attachment to the RequestSoapContext.Attachments collection (as we can see in the second method listed below).
This article explained how to use the new Web Service Enhancements from Microsoft to perform uploading and downloading of files with Web Services using the industry standard specification DIME. I also gave you a demonstration of how you could create a practical file-sharing application that could be used in an intranet environment.
Also, within the download package I provided you with the Web Service, the Win Forms client, and an additional modified version for the Web. This allows for your Web site or Intranet application to connect to the Web Service and for you to interact with it via a Web form.
Robert Chartier has developed IT solutions for more than nine years with a diverse background in both software and hardware development. He is internationally recognized as an innovative thinker and leading IT architect with frequent speaking engagements showcasing his expertise. He's been an integral part of many open forums on cutting-edge technology, including the .NET Framework and Web Services. His current position as vice president of technology for Santra Technology (http://www.santra.com) has allowed him to focus on innovation within the Web Services market space.
He uses expertise with many Microsoft technologies, including .NET, and a strong background in Oracle, BEA Systems, Inc.'s BEA WebLogic, IBM, Java 2 Platform Enterprise Edition (J2EE), and similar technologies to support his award-winning writing. He frequently publishes to many of the leading developer and industry support Web sites and publications. He has a bachelor's degree in Computer Information Systems.
FTP.NET is a file-transfer component for .NET languages (such as C# or VB.NET). FTP files directly from your application - either in synchronous or asynchronous mode. All popular FTP and proxy servers are supported. C# source code available.
In the second article of his series on Indigo web services, Chris Peiris explains how to host an Indigo web service and examines the IIS, self hosting, and Windows Activation Service hosting options. He then provides step-by-step instructions and sample code for an IIS-hosted and self-hosted Indigo web service. [Read This Article][Top]
In the first part of his series on Microsoft Indigo, Chris Peiris examines the basics of SOA, explains how Indigo fits into the picture and the problems it solves. He then introduces Indigo's programming model and finishes by building a sample Indigo web service using the Microsoft .Net Framework 2.0. [Read This Article][Top]
Adnan Masood concludes his discussion of Microsoft SQL Server Analysis services and Microsoft SQL Server Reporting services. In the final part, he discusses Reporting Server web services and using custom code in reports. [Read This Article][Top]
This article explains the features of the IE Web service behavior and shows how to asynchronously communicate with an ASP.NET Web service directly from the client.
[Read This Article][Top]
Calvin Luttrell shows how to validate e-mail addresses stored in Excel 2003 and
provides a special function for solving that pesky problem Yahoo! mail servers cause. [Read This Article][Top]
This short article describes a quick and easy way to provide some security to an ASP.NET Web service by modifying its associated documentation file. [Read This Article][Top]
Kerberos authentication is the cornerstone of Windows operating system authentication architecture. Web Services Enhancement 2.0 (WSE 2.0) extends Kerberos support to ASP.NET Web services. Chris Peiris explains the support for this new feature in WSE 2.0. [Read This Article][Top]
Chip Irek examines the architectural issues and component design issues of building a .NET application in a service-oriented architecture. [Read This Article][Top]
Thiru Thangarathinam shows how to use asynchronous Web services, Windows
Service applications, server-based timer components and .NET XML API classes to create high-performance, scalable, and flexible applications. [Read This Article][Top]
Part one showed how to transform XML data into HTML by using an XSL stylesheet from within a .NET application. This part explains how to make use of XSLT Extension objects and invoke a C# class method from an XSL stylesheet. [Read This Article][Top]
Mailing List
Want to receive email when the next article is published? Just Click Here to sign up.