Archive for July, 2009

Calling an STA COM Object from a WCF Operation

One of the things that many people are still doing is making use of old COM objects that run in STA (single threaded apartment) threads. Back in October, 2006, Jeff Prosise wrote how to do this from ASMX. Not too long after that, I had a chance to teach for Wintellect and Jeff asked me to show him how to do the same in WCF. That information went up on a post for a consulting company that didn’t make it through the latest recession. For better or worse, that post was referenced a fair number of times and now, folks are writing to me, asking for the code again.

In general, any time you receive a message via WCF, the message itself will be processed on a MTA (multi-threaded apartment) thread. Normally, this is just fine. Sometimes, you might be calling out to a COM object. COM objects will not run if the threading model the COM object needs differs from the threading model of the calling thread. MTA COM objects work just fine. But, if you have a bunch of STA COM objects (typically produced by Visual Basic but sometimes coming from C++ applications or other utilities) that you use in your WCF service, you have a problem. To allow things to work, the method needs to be invoked on an STA thread.

In .NET, one creates an STA thread by setting the apartment state of the thread prior to Start()-ing the thread.

thread.SetApartmentState(ApartmentState.STA);

thread.Start();

Now, how do we make this happen in WCF? We need to wrap the invocation of the actual method! For this, we resort to an IOperationBehavior. One typically applies a behavior (IServiceBehavior, IContractBehavior, IOperationBehavior) via an attribute. The only exception to this is an IEndpointBehavior, which is applied via manipulation of the EndpointDescription or in the app|web.config file. The IOperationBehavior exposes four methods:

  1. AddBindingParameters(OperationDescription, BindingParameterCollection): In our case, we will just do nothing with this info.
  2. ApplyClientBehavior(OperationDescription, ClientOperation): Only called when the contract is used on a client. We will  do nothing here since we only care about the service implementation.
  3. ApplyDispatchBehavior(OperationDescription, DispatchOperation): Only called when the contract is used on the server. We will do our work here and override the IOperationInvoker on the DispatchOperation so that our operation is invoked on an STA thread.
  4. Validate(OperationDescription): Used to make sure that everything is OK before applying the behavior. Normally, one throws an exception from this method if the environment isn’t right in some way. Our implementation will throw if the method we are calling is being executed asynchronously. If you need an async version of the attribute, that work is left as an exercise for you, dear reader.

Our IOperationBehavior then looks like this:

public class STAOperationBehaviorAttribute: Attribute, IOperationBehavior

{

    public void AddBindingParameters(OperationDescription operationDescription,

      System.ServiceModel.Channels.BindingParameterCollection bindingParameters)

    {

    }

 

    public void ApplyClientBehavior(OperationDescription operationDescription,

      System.ServiceModel.Dispatcher.ClientOperation clientOperation)

    {

        // If this is applied on the client, well, it just doesn’t make sense.

        // Don’t throw in case this attribute was applied on the contract

        // instead of the implementation.

    }

 

    public void ApplyDispatchBehavior(OperationDescription operationDescription,

      System.ServiceModel.Dispatcher.DispatchOperation dispatchOperation)

    {

        // Change the IOperationInvoker for this operation.

        dispatchOperation.Invoker = new STAOperationInvoker(dispatchOperation.Invoker);

    }

 

    public void Validate(OperationDescription operationDescription)

    {

        if (operationDescription.SyncMethod == null)

        {

            throw new InvalidOperationException("The STAOperationBehaviorAttribute " +

                "only works for synchronous method invocations.");

        }

    }

}

So far, so good. Now, we need to write that IOperationInvoker. You’ll note from the ApplyDispatchBehavior above that our STAOperationInvoker takes an IOperationInvoker in the constructor. This is done because, in general, the IOperationInvoker WCF gives us does everything right. It just needs to do its thing on an STA thread. Our implementation will delegate as much work as possible to the provided IOperationInvoker. This is a pattern you will follow in most WCF extensions as most extensions require a slight tweak to existing behavior. We do just that, except for our implementation of Invoke. In that, we will setup an STA thread and then call the contained IOperationInvoker’s Invoke method from the STA thread.

public class STAOperationInvoker : IOperationInvoker

{

    IOperationInvoker _innerInvoker;

    public STAOperationInvoker(IOperationInvoker invoker){

        _innerInvoker = invoker;

    }

 

    public object[] AllocateInputs()

    {

        return _innerInvoker.AllocateInputs();

    }

 

    public object Invoke(object instance, object[] inputs, out object[] outputs)

    {

        // Create a new, STA thread

        object[] staOutputs = null;

        object retval = null;

        Thread thread = new Thread(

            delegate(){

                retval = _innerInvoker.Invoke(instance, inputs, out staOutputs);

            });

        thread.SetApartmentState(ApartmentState.STA);

        thread.Start();

        thread.Join();

        outputs = staOutputs;

        return retval;

    }

 

    public IAsyncResult InvokeBegin(object instance, object[] inputs,

      AsyncCallback callback, object state)

    {

        // We don’t handle async…

        throw new NotImplementedException();

    }

 

    public object InvokeEnd(object instance, out object[] outputs, IAsyncResult result)

    {

        // We don’t handle async…

        throw new NotImplementedException();

    }

 

    public bool IsSynchronous

    {

        get { return true; }

    }

}

To test, I wrote the following Service Contract:

[ServiceContract]

public interface ITestService

{

    [OperationContract]

    string GetApartmentTypeMTA();

 

    [OperationContract]

    string GetApartmentTypeSTA();

}

and applied the attribute to this implementation:

class TestService : ITestService

{

    public string GetApartmentTypeMTA()

    {

        return Thread.CurrentThread.GetApartmentState().ToString();

    }

 

    [STAOperationBehavior]

    public string GetApartmentTypeSTA()

    {

        return Thread.CurrentThread.GetApartmentState().ToString();

    }

}

When calling the service, GetApartmentTypeMTA always returns MTA and GetApartmentTypeSTA always returns STA.

If you want to save some typing time or see the sample application, go here.

4 Comments

Slides and Presentation from CNUG, July 15 meeting

The slides and code have been posted for both the back to basics and the REST Server side talk. Click here to get them. I’d like to thank everyone for showing up. I’ll be presenting the same talk again for the Rockford .NET Users Group. You can register for the talk here.

Leave a comment

Article on using Google AppEngine + Silverlight

I wrote an article on how to integrate Google AppEngine with Silverlight. If you understand how to use REST, this integration is really easy. http://www.informit.com/articles/article.aspx?p=1354698 has the full article!

Leave a comment

Speaking at the Madison .NET User Group

The Madison, WI .NET User group has asked me to speak at their July 8 meeting. I’ll be talking about the technologies that .NET makes available for writing RESTful .NET Services:

  • ASP.NET/IIS
  • MVC
  • WCF
  • ADO.NET Data Services

I’ll be skipping the Azure/Cloud specific choices since I only have an hour to cover things. My feeling is that the audience will be hosting all of their own services for the time being, so the above 4 items will be the most interesting to them. If you are interested in seeing the talk, please sign up here. I hope to see a few of you out there.

Leave a comment

Follow

Get every new post delivered to your Inbox.