WCF and Static Classes for Large Objects

Consider the following WCF programming scenario. You want a very large data object (for example an array with several hundred thousand cells, or a huge social network graph) to reside on the WCF server. Because initialization is time-consuming you want to create the large data object only once. You don’t want to pass the large object to the WCF client but you do need to expose methods which operate on the large object (for example, a method which accepts two array index values and returns whether or not the values in the underlying data array are equal or not). One possible approach to this scenario is to wrap the class which holds the large data object with a static class. The static wrapper class will be initialized only once when the WCF service starts (well, there’s really more to it than that but I’ll skip the ugly details). If you expose a reference to the large data object you can code methods without creating new instances.

I found many questions on the Internet where people were asking how to solve WCF scenarios related to the one I described above, but found no complete solutions. However on one blog, a WCF guru, Richard Blewett, suggested the possibility of a clever static wrapper class approach to the similar problem of passing a reference to a large object on a WCF server to a WCF client. After quite a bit of experimentation I was able to implement this idea.

Here’s a concrete example where I create a large array-based object on a WCF server and access it on a client. First I use Visual Studio to create a new WCF Service named LargeObjectService which is hosted in IIS. A self-host approach will work too. In the auto-generated IService.cs file I add contracts for two example methods which operate on a large array; one to fetch a value from the large object and one to determine if the values at two indexes are the same or not:

using System; // other using statements

[ServiceContract]
public interface IService
{
  [OperationContract]
  double ValueAt(int i);
  
  [OperationContract]
  bool AreEqual(int i, int j);
}

Now in the same file, right below the interface I define the underlying class which contains the large object just as I would define any normal class. I name it SomeObject:

public class SomeObject
{
  private double[] data; // simulates a large object
  
  public SomeObject() 
  {     
    this.data = new double[10000];
    for (int i = 0; i < 10000 ; ++i) 
    {
      if (i % 2 == 0) this.data[i] = 2.0;
      else this.data[i] = 1.0;
    }
  }

  public double ValueAt(int i)
  { 
    return this.data[i];
  }

  public bool AreEqual(int i, int j) 
  { 
    return (this.data[i] == this.data[j]);
  }
}

In this dummy example I have an array of 10,000 doubles which is initialized so that cells with even indexes are assigned the value 2.0 and cells at odd indexes are assigned 1.0. Now, still in file IService.cs, just below the normal class definition I define a special static wrapper class named SomeObjectHolder:

[DataContract] 
static public class SomeObjectHolder
{
  static SomeObject theObject;

  static SomeObjectHolder()
  { 
    theObject = new SomeObject(); 
  }

  [DataMember]
  public static SomeObject GetTheObject
  { 
    get { return theObject; }
  }
}

This is the key technique suggested by Blewett. Because this is a static class it will be instantiated just once. The static wrapper class constructor calls the large object class constructor, therefore the large object constructor will be called only once. The static wrapper class uses a property GetTheObject to return a reference to the underlying data class so we can avoid making copies of the large data object.

Next I go to the auto generated Service.cs file to code the ValueAt and AreEqual methods like so:

using System; // other using statements

public class Service : IService
{
  public double ValueAt(int i)
  {
    SomeObject so = SomeObjectHolder.GetTheObject;
    return so.ValueAt(i);
  }

  public bool AreEqual(int i, int j)
  {
    SomeObject so = SomeObjectHolder.GetTheObject;
    return so.AreEqual(i, j);
  }
}

Inside each method I begin by getting a reference to the underlying data object SomeObject by using the GetTheObject property in the static wrapper class. Once I have that reference I can use it to call the underlying ValueAt and AreEqual methods in the data class. Neat! Let me mention that there are many, many variations to the approach I’ve just presented.

Now if I haven’t made any mistakes I can build the LargeObjectService WCF project, and then by hitting F5 I can get Visual Studio to display the location of new service.

To test the service I launch a new instance of Visual Studio and create a WinForm application named TestLargeObjectService. I could have made a Console App, or a WPF app, or an ASPX app, or anything else that can consume a WCF service, but I like WinForms for testing. In the Solution Explorer window I tell the application about the WCF service by right-clicking on the bold type TestLargeObjectService project name and selecting Add Service Reference from the context menu, pasting the location of the service into the dialog box, clicking Go. I like to rename the auto-generated reference name from ServiceReference1 to the slightly shorter ServiceRef.

In the WinForm designer I add a Button control and a ListBox control. I double-click on the Form object to create its event handler. Just below the first line of the Form definition I declare a static class-scope ServiceClient object:

public partial class Form1 : Form
{
  static ServiceRef.ServiceClient sc;

  public Form1()
  {
    InitializeComponent();
  }
  . . . (etc.)

Then inside the Form’s load event handler I instantiate the ServiceClient object:

private void Form1_Load(object sender, EventArgs e)
{
  sc = new ServiceRef.ServiceClient();
}

Now, when the Form loads, the ServiceClient will be instantiated. This assumes the WCF service has been running for long enough for the large data object to get initialized. Next, in the WinForm designer I double-click on the Button control to create its event handler and add code that uses the WCF service:

private void button1_Click(object sender, EventArgs e)
{   
  try   
  {     
    bool b1 = sc.AreEqual(0, 1); 
    listBox1.Items.Add("[0] and [1] equal: " + b1);
    bool b2 = sc.AreEqual(4, 8);
    listBox1.Items.Add("[4] and [8] equal: " + b2);

    double v = sc.ValueAt(5);
    listBox1.Items.Add("Value at [5]: " + v.ToString("F1"));

    //sc.Close();
  }
  catch (Exception ex)
  { 
    listBox1.Items.Add(ex.Message);
  } 
}

When the WinForm application is run, when a user clicks on the Button control, the output in the ListBox control is:

[0] and [1] equal: False
[4] and [8] equal: True
Value at [5]: 1.0

Recall that even indexes hold the value 2.0 and odd indexes hold 1.0 so the results are correct. Notice that with the design pattern I’ve used, calling the WCF service is normal in the sense that the calling code doesn’t have to do anything unusual.

If the WCF client needs a reference to the actual large object, as opposed to methods which operate on the object, one approach is to declare a method to do so in the interface:

[OperationContract]
SomeObject GetRefToSomeObject();

Then implement the method using the property to get a reference:

public SomeObject GetRefToSomeObject()
{ 
  return SomeObjectHolder.GetTheObject;
}

On the client you can write code like:

static ServiceRef.SomeObject so;
so = sc.GetRefToSomeObject(); 
bool result = SomeMethodThatNeedsObjectRef(so, 1.0, 2.0);

I’ve left out lots and lots of details, alternatives, and significant variations, but this example should get you started if you ever need to work with WCF and large data objects.

This entry was posted in Software Test Automation. Bookmark the permalink.