by Heathesh
21. July 2010 20:22
The need arose for me to run a method that used the WebBrowser object within a web service. The problem that I had is that you cannot run a WebBrowser object unless it's run in a single-threaded apartment. Within a windows forms or console application it's easy enough to set the apartment state by decorating your method with the [STAThread] attribute or using:
//set the current thread's apartment state
Thread.CurrentThread.SetApartmentState(ApartmentState.STA);
That does not work within a web service. My method also needed to accept parameters and to return a value. The method I needed to run looked like this:
public static byte[] GenerateScreenshot(string url, CustomImageFormat imageFormat, int width, int height)
This method was contained within a manager class I had created called GraphicsManager. As you can see it returns a byte[] and needs to accept four parameters. To call this method from the web service I first created a static private variable (make sure you create static variables) in my web service class for each of the parameters as well as the byte array like so:
/// <summary>
/// The returned image
/// </summary>
private static byte[] _returnedImage = null;
/// <summary>
/// The url
/// </summary>
private static string _url = string.Empty;
/// <summary>
/// The custom image format
/// </summary>
private static CustomImageFormat _imageFormat = CustomImageFormat.Gif;
/// <summary>
/// The width
/// </summary>
private static int _width = -1;
/// <summary>
/// The height
/// </summary>
private static int _height = -1;
I set default values for the variables simply out of habit. Next I created a single-threaded apartment method to execute my manager method like so. Notice that I decorated the method with the [STAThread] attribute and that the method is also a static method:
/// <summary>
/// Static method to run under Apartment State
/// </summary>
[STAThread]
private static void generateScreenShot()
{
_returnedImage = GraphicsManager.GenerateScreenshot(_url, _imageFormat, _width, _height);
}
As you can see I'm using the private variables I setup above in call to GraphicsManager.GenerateScreenshot. The last thing I needed to do was to create the WebMethod on my web service itself. I did this like so:
/// <summary>
/// Generates a portion of a screen shot image of the URL specified with with width and height specified and returns the byte stream of it in the specified format
/// </summary>
/// <param name="url">String of the URL to thumb nail</param>
/// <param name="imageFormat">CustomImageFormat enum of the image format to return</param>
/// <param name="width">Int of the width of the image to be returned</param>
/// <param name="height">Int of the height of the image to be returned</param>
/// <returns>Byte array of the thumb nail in the CustomImageFormat specified</returns>
[WebMethod(Description = @"Generates a portion of a screen shot image of the URL specified with with width and height specified and returns the byte stream of it in the specified format.")]
public byte[] GenerateWebsiteScreenshot(string url, CustomImageFormat imageFormat, int width, int height)
{
//set the private property values to the values passed in
_url = url;
_imageFormat = imageFormat;
_width = width;
_height = height;
Thread apartmentThread = new Thread(new ThreadStart(generateScreenShot));
//set the apartment state to single-threaded apartment
apartmentThread.SetApartmentState(ApartmentState.STA);
apartmentThread.Start();
//block the calling thread so as to wait for the method to complete processing
apartmentThread.Join();
//return the private property value that was generated in the apartment state thread
return _returnedImage;
}
That was it. My web service method could now run and utilize the web browser object with no problems...
Happy threading!