In my previous contribution I took a look at calling web-services. There I
showed how to asynchronously invoke a web-service from a Windows form. Behind
the scenes the project involved a lot of threading issues. The .NET framework is
very good at handling these but there is one thing I overlooked. In this paper I
will dive deeper into the matter and build build a more complicated service
which serves jpeg pictures. A windows form consumer will make asynchronous invocations
of the web-service to display the pictures on a form. I will show you how to handle binary data in a
web-service without tripping
over the threads.
What was the problem ?
When invoking a web-service by just calling the web-method the calling
application will freeze until the web-service returns a result. As web-services
not always return that fast, if they return at all, your application will be
more responsive if it makes an asynchronous call to the service. This is done
using delegate objects, objects which wrap up a callback-function or
event-handler. The web-service gets invoked and the application can continue
with whatever it wants to do. When the web-service is finished the callback will
invoke the delegate object to actually execute the method originally passed in its
constructor. In my implementation the delegates' method added the result string of the
web-service to a listbox on the windows form.
public
void WsCallback(IAsyncResult ar)
{
localhost.Service1 ws = ar.AsyncState
as localhost.Service1;
listBox1.Items.Add(ws.EndDoSomething(ar));
} |
I overlooked the fact that this callback function is executed in a worker
thread of the web-service wrapper. Which is not the same thread as the UI-thread
on which the listbox was created. This is not thread-safe, accessing windows
controls (to be precise everything which has a windows handle) is only
thread-safe from the thread on which the control was created. If you clicked
fast enough in the previous demo project it could be possible to get an asynchronous
execution error.
The control can be safely accessed from another thread by calling the control's
Invoke method. You don't have to know at coding time if you have to use this
method, the Controls class in the .NET
framework has an InvokeRequired property which will tell you at run time. The invoke method takes another delegate object
to wrap up the code to execute. In the second parameter to Invoke the arguments
to the delegate method are passed. Invoke will make the thread switch and call
the method. To solve my problem I can even reuse the callback method:
public
void WsCallback(IAsyncResult ar)
{
if (this.InvokeRequired)
{
object[]
args = new object[1];
args[0] = ar;
this.Invoke(
new AsyncCallback(WsCallback), args );
}
else
{
localhost.Service1 ws =
ar.AsyncState as localhost.Service1;
listBox1.Items.Add(ws.EndDoSomething(ar));
}
} |
The method first checks the InvokeRequired property of this.
This being the form itself. When the callback method is called from the
web-service wrapper, from a worker thread, the property will read true. The
arguments to the method to invoke is an array of objects. I construct an array
with one item to pass through the ar parameter. Now I call
the Invoke method of the form passing it a new delegate based on this
same method and its arguments. The result will be my code re-entering this
method but now from the UI-thread. The InvokeRequired property will read false
and I can safely access the listbox.
A web service which serves pictures
In the remainder of this paper I will build another web service and a consumer
which will asynchronously invoke it. In the process we will
learn more about delegates and about transporting binary data in a web service's
result. The new web service is a picture server. It has
two web-methods, one provides a list of pictures available and the other provides the image data. The pictures are published in a virtual directory on my
localhost web server, named /galleries/Family. The actual files are located in a
subdirectory (Portret) of My Pictures. When browsing //localhost//galleries/Family
with IE you see a listing of the files, by clicking them you can view them
one at a time in IE. When a web service wants to access the picture files it will not be IE
accessing them but the aspnet worker process. This process does not have any
rights outside of IIS, I explicitely have to give the aspnet account reading
rights in the directory where the files
themselves are stored.

Now the web service's implementation can open the picture files.
Publishing the pictures as an XML dataset
The nice thing about an XML dataset is that it provides metadata on the data
it contains, it describes it's own content. In the VS.NET schema editor I design a dataset
to store a
collection of pictures. In the editor I create a new Pictures element and add a
name and graphics element to this.

The name is just a string, the graphics element is of type
base64Binary. In this element you can store any binary object, like a jpg - file, as a
serializable array of bytes.
The typed dataset DataSetPictures has a table Pictures which can
store a number of pictures. Any dataset can be serialized, so it can be sent over
the HTTPprotocol as result of a web service. Now it is time to create this web service. For the details of doing that I
refer to another paper. Having the design surface of the service at hand I drag a dataset from the data tab of the toolbox. VS will propose the typed
dataset I had just created.

Which is exactly what I need, a DataSetPictures component available to all
web-methods.
A webmethod which returns a list of pictures
The PicturesList webmethod constructs a list of files found in the
directory. The method returns this list as a typed DataSetPictures dataset. The
filenames are stored in the name field. To find the files the method needs the
actual directory, which is computed using the Context.Server property of
the web-service.
private
string PictureDir
{
get
{
return
Context.Server.MapPath("/galleries/Family");
}
} |
This computation is implemented as a private property of the web-service,
making it easy to use in the WebMethod's. The DirectoryInfo class is used to
find out what can be be found in a directory. It takes the physical path,
as provided by the property, in its constructor. The GetFiles method
returns an array of files found which are enumerated in a foreach loop:
| [WebMethod]
public DataSetPictures PictureList()
{
DirectoryInfo pictDir = new
DirectoryInfo(PictureDir);
FileInfo[] pictFiles = pictDir.GetFiles();
foreach (FileInfo
pictFile in pictFiles)
{
dataSetPictures1.Pictures.AddPicturesRow(pictFile.Name,
null);
}
return dataSetPictures1;} |
dataSetPictures1 is the typed dataset component. Is has a Pictures
property which is the typed table of files. This has an AddPicturesRow
method which adds a new row, taking the field values in the parameter list. For
now I only add the name of the file. You could add a thumbnail preview of the
picture in the second parameter, for the sake of demo I will just pass a null.
Consuming the picturelist
The consumer of the web-service is a Windows application. It is a form with a
listbox and the webservice is in it's references list. I drag a dataset on the form
and again VS proposes the typed dataset DataSetPictures found in the web service referenced.
This component houses the picture data. The listbox is bound to the dataset, it
will show a list of
pictures the web service has to offer.
When the form is opened the dataset is filled. In the form_load event the webservices is
invoked. As I perform an asynchronous invocation the loading of the form will
not be paused waiting for the web service to return the actual list.
private
void Form1_Load(object
sender, System.EventArgs e)
{
localhost.PictureServices ws =
new localhost.PictureServices();
ws.BeginPictureList(new
AsyncCallback(ReceivePictureList), ws);
} |
When the webservice is finished it will call-back the ReceivePictureList
method.
private
void ReceivePictureList(IAsyncResult ar)
{
localhost.PictureServices ws = ar.AsyncState
as localhost.PictureServices;
lock(this)
{
dataSetPictures1.Merge(ws.EndPictureList(ar).Pictures);
}
} |
The method receives the dataset. Before merging the Pictures with the
dataSetPictures1 component it has to lock the current form object. During the
merge the state of the dataset component is undefined. No other threads (like a
callback from another web service invocation) can access the current
object until the end of the lock. The merge statement is in a so called critical
section, threads pass these one at a time. For more info
threading you are invited to my
threading article.
A webmethod which returns graphics
The second web-method Picture, does return the actual picture. It
wraps it up in a dataset of the same type as we met in PictureList , this time
the graphics field will contain the image. The method's parameter indicates
which file(s) to return.
| [WebMethod]
public DataSetPictures Picture(string
FileName)
{
DirectoryInfo pictDir = new
DirectoryInfo(PictureDir);
FileInfo[] pictFiles = pictDir.GetFiles(FileName);
foreach (FileInfo
pictFile in pictFiles)
{
long
pictSize = pictFile.Length;
byte[]
buffer = new byte[pictSize];
System.IO.FileStream fs =
new FileStream(pictFile.FullName, FileMode.Open,
FileAccess.Read);
fs.Read(buffer, 0, (int)
pictSize);
dataSetPictures1.Pictures.AddPicturesRow(pictFile.Name, buffer);
}
return dataSetPictures1;
} |
Again a DirectoryInfo object gets me to the files. This time the
GetFiles method is passed the name of the desired file(s). You can use
wildcards in this name, like picture?.jpg. The result of the
web-method is a dataset so it can return multiple files in multiple rows. All
files are treated one by one in the foreach loop. The graphics field is an
array of bytes, to determine the array size I read the length
property of the FileInfo object. To read the data in the file I create a
FileStream object based on the file's name. The full path to the file is also
in the FileInfo object. Respect the flags when opening the file, I
had given the aspnet process only read
permission. If I don't explicitly open the stream as read-only I will
receive an "access denied" error. Having opened the filestream it is read into
the buffer. Together with the name this buffer is added to the dataset.
To get an idea how the graphics data is transported in the webservice you can
take a look with your browser. Browse to
http://localhost/WebServices/WebPictureService/PictureServices.asmx?op=Picture
and invoke the picture method with parameter MyPicture1?.jpg (or whatever
you have in your pictures directory). You will receive a nice bunch of XML as a
result :

Between the <Graphics> tags there is a very long string of characters.
They are the encoded graphics. Take some care when playing with this, when your
directory contains a lot of big files you will receive an XML response of many
many megabytes; larger then IE or your PC will like.
Consuming the graphics
The windows form has two ways of requesting an image from the web service.
Double clicking an item in the listbox request the item pointed at :
private
void listBox1_DoubleClick(object
sender, System.EventArgs e)
{
localhost.DataSetPictures.PicturesRow fileName =
dataSetPictures1.Pictures[listBox1.SelectedIndex];
localhost.PictureServices ws =
new localhost.PictureServices();
ws.BeginPicture(fileName.Name,
new AsyncCallback(ReceivePicture), ws);
} |
The filename is read from the dataset the listbox is bound to. The
SelectedIndex property of the listbox will get me to the right row.
As a second option a button requests all files matching the pattern in a
textbox
private
void button1_Click(object
sender, System.EventArgs e)
{
localhost.PictureServices ws =
new localhost.PictureServices();
ws.BeginPicture(textBox1.Text,
new AsyncCallback(ReceivePicture), ws);
} |
Both methods perform an asynchronous invocation. When the web-service is
finished it invokes the ReceivePicture method to show the result. This method
may not touch any of the visual controls, to do that it will Invoke
another delegate. The method does some of the non-visual pre-processing
private
void ReceivePicture(IAsyncResult ar)
{
localhost.PictureServices ws = ar.AsyncState
as localhost.PictureServices;
localhost.DataSetPictures dsp = ws.EndPicture(ar);
object[]
invokeArgs = new Object[1];
for (int
i =0; i < dsp.Pictures.Count; i++ )
{
invokeArgs[0]
= dsp.Pictures[i];
this.Invoke(new
ShowPictureDelegate(ShowPicture), invokeArgs);
}
} |
The method catches the dataset returned by the web service and takes it apart row by row. Each
row is handled by the delegate method ShowPicture which will show the actual contents on the form.
The parameter to this method is typed as a DataSetPictures.PicturesRow
. If I want to create a delegate object for this method I have to declare a
delegate of its signature
|
delegate
void
ShowPictureDelegate(localhost.DataSetPictures.PicturesRow pr); |
For a wider explanation on delegates you are invited to
another of
my dotnetjunkies stories. Here I create a parameter list containing the row
and pass it with the delegate to the Invoke method. The delegate is created on
the ShowPicture method. In there the real display work is done. On the
form is an empty tabControl. For every picture it adds a new tabPage to the
control. On the tabpage will be a PictureBox control to house the
image. During the process, the tabControl will be in an undetermined state. By
locking the form it's access is serialized, different threads will execute
one after the other.
private
void
ShowPicture(localhost.DataSetPictures.PicturesRow pr)
{
lock(this) {
TabPage newTabPage = new
System.Windows.Forms.TabPage();
newTabPage.Text = pr.Name;
tabControl1.Controls.Add(newTabPage);
PictureBox newPictureBox = new
System.Windows.Forms.PictureBox();
newTabPage.Controls.Add(newPictureBox);
byte[] buffer
= new Byte[pr.Graphics.Length];
MemoryStream ms = new
MemoryStream(buffer);
ms.Write(pr.Graphics, 0, pr.Graphics.Length);
newPictureBox.SizeMode =
PictureBoxSizeMode.AutoSize;
newPictureBox.Image = Image.FromStream(ms);
tabControl1.SelectedIndex =
tabControl1.TabPages.Count -1;
}
} |
The method creates a new TabPage. The title is set to the name of the
picture. The tabpage becomes visible by adding it to the Controls collection of the
tabControl. To display the picture a PictureBox is
created and added to the Controls collection of the tabpage.
To catch the graphics I set up an array of bytes, the size of the buffer
is in the Length property of the graphics field. I create a MemoryStream
on this buffer and fill it with the contents of the Graphics field. This stream is used to fill the Image property of the
PictureBox. This filling is done using the static FromStream method of
the Image class. You use static methods (of the Image class) without
referring to an actual image object.
The result is a tabbed picture album whose content is supplied by
a web service.

To conclude
In this paper I have explored the world of web-services a little farther.
Working with strongly typed datasets to communicate between service and consumer
has lead to plain strongly typed OOP code. In these datasets multiple binary
large objects (BLOB's) are transferred in one go. Transferring these large
amounts of data could lead to a freezing consumer. The consuming application
stays responsive by invoking the web-service asynchronously. Which does result in some
threading issues, all of these can be solved in an elegant manner using
locks and delegates.