Rick Strahl's Weblog  

Wind, waves, code and everything in between...
.NET • C# • Markdown • WPF • All Things Web
Contact   •   Articles   •   Products   •   Support   •   Advertise
Sponsored by:
West Wind WebSurge - Rest Client and Http Load Testing for Windows

Creating STA COM compatible ASP.NET Applications


:P
On this page:

When building ASP.NET applications that interface with old school COM objects like those created with VB6 or Visual FoxPro (MTDLL), it's extremely important that the threads that are serving requests use Single Threaded Apartment Threading. STA is a COM built-in technology that allows essentially single threaded components to operate reliably in a multi-threaded environment. STA's guarantee that COM objects instantiated on a specific thread stay on that specific thread and any access to a COM object from another thread automatically marshals that thread to the STA thread. The end effect is that you can have multiple threads, but a COM object instance lives on a fixed never changing thread.

ASP.NET by default uses MTA (multi-threaded apartment) threads which are truly free spinning threads that pay no heed to COM object marshaling. This is vastly more efficient than STA threading which has a bit of overhead in determining whether it's OK to run code on a given thread or whether some sort of thread/COM marshaling needs to occur. MTA COM components can be very efficient, but STA COM components in a multi-threaded environment always tend to have a fair amount of overhead.

It's amazing how much COM Interop I still see today so while it seems really old school to be talking about this topic, it's actually quite apropos for me as I have many customers using legacy COM systems that need to interface with other .NET applications. In this post I'm consolidating some of the hacks I've used to integrate with various ASP.NET technologies when using STA COM Components.

STA in ASP.NET

Support for STA threading in the ASP.NET framework is fairly limited. Specifically only the original ASP.NET WebForms technology supports STA threading directly via its STA Page Handler implementation or what you might know as ASPCOMPAT mode. For WebForms running STA components is as easy as specifying the ASPCOMPAT attribute in the @Page tag:

<%@ Page Language="C#" AspCompat="true" %>

which runs the page in STA mode. Removing it runs in MTA mode. Simple.

Unfortunately all other ASP.NET technologies built on top of the core ASP.NET engine do not support STA natively. So if you want to use STA COM components in MVC or with class ASMX Web Services, there's no automatic way like the ASPCOMPAT keyword available.

So what happens when you run an STA COM component in an MTA application? In low volume environments - nothing much will happen. The COM objects will appear to work just fine as there are no simultaneous thread interactions and the COM component will happily run on a single thread or multiple single threads one at a time. So for testing running components in MTA environments may appear to work just fine.

However as load increases and threads get re-used by ASP.NET COM objects will end up getting created on multiple different threads. This can result in crashes or hangs, or data corruption in the STA components which store their state in thread local storage on the STA thread. If threads overlap this global store can easily get corrupted which in turn causes problems. STA ensures that any COM object instance loaded always stays on the same thread it was instantiated on.

What about COM+?

COM+ is supposed to address the problem of STA in MTA applications by providing an abstraction with it's own thread pool manager for COM objects. It steps in to the COM instantiation pipeline and hands out COM instances from its own internally maintained STA Thread pool. This guarantees that the COM instantiation threads are STA threads if using STA components.

COM+ works, but in my experience the technology is very, very slow for STA components. It adds a ton of overhead and reduces COM performance noticably in load tests in IIS. COM+ can make sense in some situations but for Web apps with STA components it falls short. In addition there's also the need to ensure that COM+ is set up and configured on the target machine and the fact that components have to be registered in COM+. COM+ also keeps components up at all times, so if a component needs to be replaced the COM+ package needs to be unloaded (same is true for IIS hosted components but it's more common to manage that).

COM+ is an option for well established components, but native STA support tends to provide better performance and more consistent usability, IMHO.

What about HttpRuntime's apartmentThreading Key?

You can specify the following in Web.Config:

<system.web>
<httpRuntime apartmentThreading="true"/> </system.web>

Looks promising right? Unfortunately it appears that this only works as a global flag for WebForms and is equivalent of specifying ASPCOMPAT in all WebForms pages. It has no effect on MVC, Web Services, WCF, Handlers etc. unfortunately.

STA for non supporting ASP.NET Technologies

As mentioned above only WebForms supports STA natively. However, by utilizing the WebForms ASP.NET Page handler internally it's actually possible to trick various other ASP.NET technologies and let them work with STA components. This is ugly but I've used each of these in various applications and I've had minimal problems making them work with FoxPro STA COM components which is about as dififcult as it gets for COM Interop in .NET.

In this post I summarize several STA workarounds that enable you to use STA threading with these ASP.NET Technologies:

  • ASP.NET HttpHandlers
  • ASMX Web Services
  • ASP.NET MVC
  • WCF Web Services
  • ASP.NET Web API

ASP.NET HttpHandlers

HttpHandlers are pretty low to the metal in ASP.NET: They are the lowest level hookable endpoint in ASP.NET for writing application level code. Handlers tend to be for framework level components that build higher level functionality on top, but they can also be used for the highest performance operations that don't need the overhead of higher level frameworks like MVC, WebForms, WebAPI etc. In pure throughput HttpHandlers are as fast as it gets for ASP.NET requests.

As such I'm not sure how useful this is for STA components since by definition STA COM components have a fair bit of overhead when called compared to native .NET code so that diminshes much of the performance benefit of handlers in the first place.

Nevertheless, the base STA Handler implementation demonstrates the core trick that is used to allow STA capable ASP.NET code. The trick to this is the ASP.NET WebForms System.Web.UI.Page class which is an HttpHandler implementation and which also supports an ASPCOMPAT mode that provides STA threads to WebForms pages. As it turns out the STA functionality is wired into the Page class itself via the AspCompatBeginRequest() and AspCompatEndRequest() methods which can be fired from any HttpHandler implementation.

To create an Sta capable handler implementation the following code can be used:

using System;
using System.Web;
using System.Web.SessionState;

namespace FoxProAspNet
{
    /// <summary>
    /// This handler allows SOAP Web Services to run in STA mode
    /// 
    /// Note: this will NOT work with ASP.NET AJAX services as
    /// these services bypass the handler mapping and perform
    /// all work in a module (RestModule).
    /// </summary>
    public abstract class StaHttpHandler :
        System.Web.UI.Page, IHttpAsyncHandler
    {
        protected override void OnInit(EventArgs e)
        {
            this.ProcessRequestSta(this.Context);

            // immediately stop the request after we're done processing
            this.Context.ApplicationInstance.CompleteRequest();
        }

        /// <summary>
        /// This actually should never fire but has to be here for IHttpHandler
        /// </summary>
        /// <param name="context"></param>
        public void ProcessRequest(HttpContext context)
        {
            // internally runs async request so Begin/EndProcessRequest are called
            base.ProcessRequest(context);
        }

        /// <summary>
        /// This method should receive the real processing code
        /// </summary>
        /// <param name="context"></param>
        public abstract void ProcessRequestSta(HttpContext context);


        /// <summary>
        /// This is what handles the STA thread creation/pool thread queing
        /// </summary>
        /// <param name="context"></param>
        /// <param name="cb"></param>
        /// <param name="extraData"></param>
        /// <returns></returns>
        public IAsyncResult BeginProcessRequest(
            HttpContext context, AsyncCallback cb, object extraData)
        {
            return this.AspCompatBeginProcessRequest(context, cb, extraData);
        }


        public void EndProcessRequest(IAsyncResult result)
        {
            this.AspCompatEndProcessRequest(result);
        }
    }

    /// <summary>
    /// Subclass this if your handler needs session state
    /// </summary>
    public class StaHttpHandlerWithSessionState :
                    StaHttpHandler, IRequiresSessionState
    {
        public override void ProcessRequestSta(HttpContext context)
        {
        }

    }

}

To implement the class simply inherit from StaHandler:

public class StaHandler : StaHttpHandler
{
    public void ProcessRequest(HttpContext context)
    {
        ProcessRequestSta(context);
    }

    public override void ProcessRequestSta(HttpContext context)
    {
        context.Response.ContentType = "text/plain";
        context.Response.Write("Hello World " + Thread.CurrentThread.GetApartmentState());
    }     
}

The StaHandler implementation basically overrides the BeginProcessRequest and EndProcessRequest methods and routes the AspCompatBeginRequest/EndRequest methods. By doing so the request gets an STA thread. BeginProcessRequest implicitly fires the OnInit where we can then start executing STA safe code. My base class simply routes to an alternate ProcessRequestSta method which can be implemented in an inherited class.

Originally I thought we should just be able to fire this.ProcessRequest() directly, but it turns out that this causes OnInit() to be fired again, resulting in an eventual StackOverflow. So in order to make this work a method with a different name is required.

ASMX Web Services

ASMX Web Services can use basically the same trick used above, by creating a custom handler class and then instantiating the WebService handler from it and firing it from the Init. WebService itself isn't the handler so the process of getting the handler requires invoking the WebService - actually the ScriptService (that also supports ASP.NET AJAX Services) -HandlerFactory and then calling its ProcessRequest method.

Here's the code for this process:

(updated to ensure that ASP.NET AJAX Services can still run. Note ASP.NET AJAX is not supported for STA operation)

namespace FoxProAspNet
{
    /// <summary>
    /// This handler allows SOAP Web Services to run in STA mode
    /// 
    /// Note: this will NOT work with ASP.NET AJAX services as
    /// these services bypass the handler mapping and perform
    /// all work in a module (RestModule).
    /// </summary>
    public class WebServiceStaHandler :
        System.Web.UI.Page, IHttpAsyncHandler
    {
        /// <summary>
        /// Cache the HttpHandlerFactory lookup
        /// </summary>
        static IHttpHandlerFactory _HttpHandlerFactory = null;
        static object _HandlerFactoryLock = new object();

        protected override void OnInit(EventArgs e)
        {
            var factory = this.GetScriptHandlerFactory();

            IHttpHandler handler =
                factory.GetHandler(
                    this.Context,
                    this.Context.Request.HttpMethod,
                    this.Context.Request.FilePath,
                    this.Context.Request.PhysicalPath);
            handler.ProcessRequest(this.Context);

            // immediately stop the request after we're done processing
            this.Context.ApplicationInstance.CompleteRequest();
        }


        /// <summary>
        /// Loader for the script handler factory
        /// </summary>
        IHttpHandlerFactory GetScriptHandlerFactory()
        {
            if (_HttpHandlerFactory != null)
                return _HttpHandlerFactory;

            IHttpHandlerFactory factory = null;

            lock (_HandlerFactoryLock)
            {
                if (_HttpHandlerFactory != null)
                    return _HttpHandlerFactory;

                try
                {
                    // Try to load the ScriptServiceHandlerFactory
                    // class is internal requires reflection so this requires full trust
                    var assembly = typeof(JavaScriptSerializer).Assembly;
                    var shf = assembly.GetTypes().Where(t => t.Name == "ScriptHandlerFactory").FirstOrDefault();
                    factory = Activator.CreateInstance(shf) as IHttpHandlerFactory;
                }
                catch { }

                // Fallback to just WebService Handler Factory
                if (factory == null)
                    factory = new WebServiceHandlerFactory();

                _HttpHandlerFactory = factory;
            }

            return factory;
        }


        public override void ProcessRequest(HttpContext context)
        {
            base.ProcessRequest(context);
        }

        public IAsyncResult BeginProcessRequest(
            HttpContext context, AsyncCallback cb, object extraData)
        {
            return this.AspCompatBeginProcessRequest(context, cb, extraData);
        }


        public void EndProcessRequest(IAsyncResult result)
        {
            this.AspCompatEndProcessRequest(result);
        }
    }

    public class AspCompatWebServiceStaHandlerWithSessionState :
                    WebServiceStaHandler, IRequiresSessionState
    {
    }

}

This class overrides the ASP.NET WebForms Page class which has a little known AspCompatBeginProcessRequest() and AspCompatEndProcessRequest() method that is responsible for providing the WebForms ASPCOMPAT functionality. These methods handle routing requests to STA threads. Note there are two classes - one that includes session state and one that does not. If you plan on using ASP.NET Session state use the latter class, otherwise stick to the former. This maps to the EnableSessionState page setting in WebForms. '

This class simply hooks into this functionality by overriding the BeginProcessRequest and EndProcessRequest methods and always forcing it into the AspCompat methods.

The way this works is that BeginProcessRequest() fires first to set up the threads and starts intializing the handler. As part of that process the OnInit() method is fired which is now already running on an STA thread. The code then creates an instance of the actual WebService handler factory and calls its ProcessRequest method to start executing which generates the Web Service result. Immediately after ProcessRequest the request is stopped with Application.CompletRequest() which ensures that the rest of the Page handler logic doesn't fire. This means that even though the fairly heavy Page class is overridden here, it doesn't end up executing any of its internal processing which makes this code fairly efficient.

In a nutshell, we're highjacking the Page HttpHandler and forcing it to process the WebService process handler in the context of the AspCompat handler behavior.

ASP.NET AJAX Web Services are not supported

Note that there's a bit of logic there that loads up the right HttpHandlerFactory. The ASMX extension actually manages both SOAP services and ASP.NET AJAX services. Unfortunately ASP.NET AJAX Services do not work using this STA routing because the actual processing for AJAX requests is actually handled in a module (ScriptModule class) and so requests actually never reach the handler. However, the logic to hook up the ScriptServiceFactory is still required if you use ASP.NET AJAX code in your application - without it the AJAX services fail to run even though the processing has already occurred (what kind of funky design is that?). The code is there merely to ensure that ASP.NET AJAX services can co-exist with STA SOAP Web Services.

Hooking up the Handler

Because the above is an HttpHandler implementation you need to hook up the custom handler and replace the standard ASMX handler. To do this you need to modify the web.config file (here for IIS 7 and IIS Express):

 <configuration>   <system.webServer>
    <handlers>
      <remove name="WebServiceHandlerFactory-Integrated-4.0" />      
      <add name="Asmx STA Web Service Handler" path="*.asmx" verb="*" 
type="FoxProAspNet.WebServiceStaHandler" precondition="integrated"/> </handlers> </system.webServer> </configuration>

(Note: The name for the WebServiceHandlerFactory-Integrated-4.0 might be slightly different depending on your server version. Check the IIS Handler configuration in the IIS Management Console for the exact name or simply remove the handler from the list there which will propagate to your web.config).

For IIS 5 & 6 (Windows XP/2003) or the Visual Studio Web Server use:

<configuration>
<system.web>
<httpHandlers> <remove path="*.asmx" verb="*" /> <add path="*.asmx" verb="*" type="FoxProAspNet.WebServiceStaHandler" /> </httpHandlers> </system.web>
</configuration>

To test, create a new ASMX Web Service and create a method like this:

    [WebService(Namespace = "http://foxaspnet.org/")]
    [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
    public class FoxWebService : System.Web.Services.WebService
    {

        [WebMethod]
        public string HelloWorld()
        {
            return "Hello World. Threading mode is: " + 
System.Threading.
Thread.CurrentThread.GetApartmentState(); } }

Run this before you put in the web.config configuration changes and you should get:

Hello World. Threading mode is: MTA

Then put the handler mapping into Web.config and you should see:

Hello World. Threading mode is: STA

And you're on your way to using STA COM components. It's a hack but it works well! I've used this with several high volume Web Service installations with various customers and it's been fast and reliable.

ASP.NET MVC

ASP.NET MVC has quickly become the most popular ASP.NET technology, replacing WebForms for creating HTML output. MVC is more complex to get started with, but once you understand the basic structure of how requests flow through the MVC pipeline it's easy to use and amazingly flexible in manipulating HTML requests. In addition, MVC has great support for non-HTML output sources like JSON and XML, making it an excellent choice for AJAX requests without any additional tools.

Unlike WebForms ASP.NET MVC doesn't support STA threads natively and so some trickery is needed to make it work with STA threads as well.

MVC gets its handler implementation through custom route handlers using ASP.NET's built in routing semantics. To work in an STA handler requires working in the Page Handler as part of the Route Handler implementation.

As with the Web Service handler the first step is to create a custom HttpHandler that can instantiate an MVC request pipeline properly:

public class MvcStaThreadHttpAsyncHandler : Page, IHttpAsyncHandler, IRequiresSessionState
{
    RequestContext reqContext;

    public MvcStaThreadHttpAsyncHandler(RequestContext requestContext)
    {
        if (requestContext == null)
            throw new ArgumentNullException("requestContext");

        reqContext = requestContext;
    }

    public IAsyncResult BeginProcessRequest(HttpContext context, AsyncCallback cb, object extraData)
    {
        return this.AspCompatBeginProcessRequest(context, cb, extraData);
    }

    protected override void OnInit(EventArgs e)
    {
        var controllerName = reqContext.RouteData.GetRequiredString("controller");
        if (string.IsNullOrEmpty(controllerName))
            throw new InvalidOperationException("Could not find controller to execute");

        var controllerFactory = ControllerBuilder.Current.GetControllerFactory();

        IController controller = null;
        try
        {
            controller = controllerFactory.CreateController(reqContext, controllerName);
            if (controller == null)
                throw new InvalidOperationException("Could not find controller: " + controllerName);

        }
        catch
        {
            throw new InvalidOperationException("Could not find controller: " + controllerName +
                                                ". Could be caused by missing default document in virtual.");
        }

        try
        {
            controller.Execute(reqContext);
        }
        finally
        {
            controllerFactory.ReleaseController(controller);
        }

        this.Context.ApplicationInstance.CompleteRequest();
    }

    public void EndProcessRequest(IAsyncResult result)
    {
        this.AspCompatEndProcessRequest(result);
    }

    public override void ProcessRequest(HttpContext httpContext)
    {
        throw new NotSupportedException();
    }
}

This handler code figures out which controller to load and then executes the controller. MVC internally provides the information needed to route to the appropriate method and pass the right parameters. Like the Web Service handler the logic occurs in the OnInit() and performs all the processing in that part of the request.

Next, we need a RouteHandler that can actually pick up this handler. Unlike the Web Service handler where we simply registered the handler, MVC requires a RouteHandler to pick up the handler. RouteHandlers look at the URL's path and based on that decide on what handler to invoke.

The route handler is pretty simple - all it does is load our custom handler:

    public class MvcStaThreadRouteHandler : IRouteHandler
    {
        public IHttpHandler GetHttpHandler(RequestContext requestContext)
        {
            if (requestContext == null)
                throw new ArgumentNullException("requestContext");

            return new MvcStaThreadHttpAsyncHandler(requestContext);
        }
    }

At this point you can instantiate this route handler and force STA requests to MVC by specifying a route. The following sets up the ASP.NET Default Route:

Route mvcRoute = new Route("{controller}/{action}/{id}",
                            new RouteValueDictionary( new { 
                                    controller = "Home", 
                                    action = "Index", 
                                    id = UrlParameter.Optional 
                                }),
                            new MvcStaThreadRouteHandler());
RouteTable.Routes.Add(mvcRoute);

 

To make this code a little easier to work with and mimic the behavior of the routes.MapRoute() functionality extension method that MVC provides, here is an extension method for MapMvcStaRoute():

    public static class RouteCollectionExtensions
    {

        public static void MapMvcStaRoute(this RouteCollection routeTable,
                                     string name,
                                     string url,
                                     object defaults = null)
        {
            Route mvcRoute = new Route(url,
                                       new RouteValueDictionary(defaults),
                                       new MvcStaThreadRouteHandler());
            RouteTable.Routes.Add(mvcRoute);
        }
    }

With this the syntax to add  route becomes a little easier and matches the MapRoute() method:

RouteTable.Routes.MapMvcStaRoute(
    name: "Default",
    url: "{controller}/{action}/{id}",
    defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);

The nice thing about this route handler, STA Handler and extension method is that it's fully self contained. You can put all three into a single class file and stick it into your Web app, and then simply call MapMvcStaRoute() and it just works. Easy!

To see whether this works create an MVC controller like this:

    public class ThreadTestController : Controller
    {        
        public string ThreadingMode()
        {
            return Thread.CurrentThread.GetApartmentState().ToString();
        }
}

Try this test both with only the MapRoute() hookup in the RouteConfiguration in which case you should get MTA as the value. Then change the MapRoute() call to MapMvcStaRoute() leaving all the parameters the same and re-run the request. You now should see STA as the result.

You're on your way using STA COM components reliably in ASP.NET MVC.

WCF Web Services running through IIS

WCF Web Services provide a more robust and wider range of services for Web Services. You can use WCF over HTTP, TCP, and Pipes, and WCF services support WS* secure services. There are many features in WCF that go way beyond what ASMX can do. But it's also a bit more complex than ASMX. As a basic rule if you need to serve straight SOAP Services over HTTP I 'd recommend sticking with the simpler ASMX services especially if COM is involved. If you need WS* support or want to serve data over non-HTTP protocols then WCF makes more sense.

WCF is not my forte but I found a solution from Scott Seely on his blog that describes the progress and that seems to work well. I'm copying his code below so this STA information is all in one place and quickly explain.

Scott's code basically works by creating a custom OperationBehavior which can be specified via an [STAOperation] attribute on every method. Using his attribute you end up with a class (or Interface if you separate the contract and class) that looks like this:

    [ServiceContract]
    public class WcfService
    {
        [OperationContract]
        public string HelloWorldMta()
        {
            return Thread.CurrentThread.GetApartmentState().ToString();
        }

        // Make sure you use this custom STAOperationBehavior
        // attribute to force STA operation of service methods
        [STAOperationBehavior]
        [OperationContract]
        public string HelloWorldSta()
        {
            return Thread.CurrentThread.GetApartmentState().ToString();
        }

    } 

Pretty straight forward. The latter method returns STA while the former returns MTA. To make STA work every method needs to be marked up.

The implementation consists of the attribute and OperationInvoker implementation. Here are the two classes required to make this work from Scott's post:

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.");
        }
    }
}

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; }
    }
}

The key in this setup is the Invoker and the Invoke method which creates a new thread and then fires the request on this new thread.

Because this approach creates a new thread for every request it's not super efficient. There's a bunch of overhead involved in creating the thread and throwing it away after each thread, but it'll work for low volume requests and insure each thread runs in STA mode. If better performance is required it would be useful to create a custom thread manager that can pool a number of STA threads and hand off threads as needed rather than creating new threads on every request.

If your Web Service needs are simple and you need only to serve standard SOAP 1.x requests, I would recommend sticking with ASMX services. It's easier to set up and work with and for STA component use it'll be significantly better performing since ASP.NET manages the STA thread pool for you rather than firing new threads for each request. One nice thing about Scotts code is though that it works in any WCF environment including self hosting. It has no dependency on ASP.NET or WebForms for that matter.

STA - If you must

STA components are a  pain in the ass and thankfully there isn't too much stuff out there anymore that requires it. But when you need it and you need to access STA functionality from .NET at least there are a few options available to make it happen. Each of these solutions is a bit hacky, but they work - I've used all of them in production with good results with FoxPro components.

I hope compiling all of these in one place here makes it STA consumption a little bit easier. I feel your pain :-)

Resources

Posted in FoxPro  ASP.NET  .NET  COM  

The Voices of Reason


 

Juan Pablo
September 25, 2012

# re: Creating STA COM compatible ASP.NET Applications

Hi Rick, thank you very much for this article. I'm trying to implement the first method (asmx web services) but I'm having a problem. Everything works fine if I test the webservice typing directly in the browser address bar. But it doesn't work if I try to invoke the method using jquery's ajax() function.

The error reported is: [ArgumentException: Unknown web method HelloWorld. Parameter name: methodName].
System.Web.Script.Services.WebServiceData.GetMethodData(String methodName) +514665
System.Web.Handlers.ScriptModule.OnPostAcquireRequestState(Object sender, EventArgs eventArgs) +168
System.Web.SyncEventExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +68
System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +75

If I change the web.config to the default configuration the ajax call works fine. Any idea what could be wrong?

Thanks in advance, Juan Pablo.

Rick Strahl
September 25, 2012

# re: Creating STA COM compatible ASP.NET Applications

Juan - Unfortunately the STA handler doesn't work with ASP.NET AJAX services. The reason for this is that AJAX Services use a module (System.Web.Handlers.ScriptModule ) and a RestHandler() to process requests before the handler is invoked. By the time the handler is fired the request is already done. :-(

However, I just saw that we do have to handle support for ScriptServiceHandlerFactory, because if it's not there then AJAX Services fail completely.

I've updated the code above to try and load the ScriptHandlerFactory if possible and only if it doesn't fall back to WebServiceHandlerFactory. Regardless though AJAX services will always run MTA.

Hope this helps.

Juan Pablo
September 26, 2012

# re: Creating STA COM compatible ASP.NET Applications

Again, thanks a lot for taking the time to answer.

But I'm not using ASP.NET AJAX. I'm using jQuery (the jquery ajax() method). I tried the modified code but I keep getting the same error: "Unknown Web Method". Invoking it via SOAP works fine.

I'll try to make a small example and send it to you.

Rick Strahl
September 27, 2012

# re: Creating STA COM compatible ASP.NET Applications

ASMX Services that use JSON are known as ASP.NET AJAX Services so yes you are using that.

Are you sure you're calling the service correctly? Parameters must be passed in via POST (even if there are no parameters!) and must be in the format of { parmname: value } sent from the client JSON.

Definitely works for me although no matter what those requests will be MTA on the server.

Juan Pablo
September 27, 2012

# re: Creating STA COM compatible ASP.NET Applications

Rick, I have found that the problem is in the parameter "contentType" of the .ajax() function. If I set it to "application/json" the call fails ("Unknown Web Method").

If I omit that parameter the call succeeds but the data is returned as XML. (I need json data).

It also works if I use the .post() function (It also returns XML)

What do you think? Could it be possible to fix it to work with contentType: "application/json"?

Again, thank you very much for taking the time to answer.

Richard
September 28, 2012

# re: Creating STA COM compatible ASP.NET Applications

If you want every request to use an STA thread, can't you just set the apartmentThreading attribute on the httpRuntime configuration element?

http://msdn.microsoft.com/en-us/library/e1f13641%28v=vs.80%29.aspx

Rick Strahl
September 28, 2012

# re: Creating STA COM compatible ASP.NET Applications

@Richard - unfortunately no. It seems this only affects WebForms and the equivalent of STACOMPAT. I had my hopes up for that one too :-) Not sure when this got introduced even, but it's pretty misleading. I just double checked: Neither ASMX or MVC respect that flag.

Spence
October 02, 2012

# re: Creating STA COM compatible ASP.NET Applications

What would be different if creating .ashx handlers?

Rick Strahl
October 03, 2012

# re: Creating STA COM compatible ASP.NET Applications

@Spence - took a quick look and although I thought we should be able to simply replace the code in the OnInit() to call this.ProcessRequest() but unfortunately that doesn't work as it ends up causing a stack overflow exception. Turns out the ProcessRequest() call triggers the OnInit() to fire which causes the endless recursion.

So, the solution to this is to create a custom handler that has a special method - like ProcessRequestSta() - that handles the actual processing.

Here's my implementation:

public abstract class StaHttpHandler :
    System.Web.UI.Page, IHttpAsyncHandler
{
    protected override void OnInit(EventArgs e)
    {            
        this.ProcessRequestSta(this.Context);
 
        // immediately stop the request after we're done processing
        this.Context.ApplicationInstance.CompleteRequest();
    }
 
    public void ProcessRequest(HttpContext context)
    {
        // internally runs async request so Begin/EndProcessRequest are called
        base.ProcessRequest(context);
    }
 
 
    public abstract void ProcessRequestSta(HttpContext context);
 
    public IAsyncResult BeginProcessRequest(
        HttpContext context, AsyncCallback cb, object extraData)
    {
        return this.AspCompatBeginProcessRequest(context, cb, extraData);
    }
 
 
    public void EndProcessRequest(IAsyncResult result)
    {
        this.AspCompatEndProcessRequest(result);
    }
}
 
public class StaHttpHandlerWithSessionState :
                StaHttpHandler, IRequiresSessionState
{
    public override void ProcessRequestSta(HttpContext context)
    {
    }
 
}


You can then simply create any handler by subclassing from StaHttpHandler and implementing the ProcessRequestStaMethod().

    /// <summary>
    /// Summary description for StaHandler
    /// </summary>
    public class StaHandler : StaHttpHandler, IHttpHandler
    {
        public void ProcessRequest(HttpContext context)
        {
            ProcessRequestSta(context);
        }
 
        public override void ProcessRequestSta(HttpContext context)
        {
            context.Response.ContentType = "text/plain";
            context.Response.Write("Hello World " + Thread.CurrentThread.GetApartmentState());
        }     
    }

Matej
October 19, 2012

# re: Creating STA COM compatible ASP.NET Applications

Scott,

Thanks for the article. Howver I think that the following statement you made is not true:
>So what happens when you run an STA COM component in an MTA application?....
>...This can result in crashes or hangs, or data corruption in the STA components which store...

If a MTA a thread (default ASP.NET threading type) creates a COM components which is marked with STA thrading modell, COM enures that this components is created on a seperate threads and gives a proxy to the client. The proxy is responsible for switching the threads on method calls. The method calls are marshaled to another thread via windows message queues. Basically this ensures, that a given instance of STA compontent is always executed on the thread it wac created on.

However you can run into a problem if you pass a direct reference (without a proxy) to a STA component to another MTA thread. For example: if you store a reference to the STA component in a static variable and use it on aother threads. If you share indirect reference, the proxy might detect that it is used from a wrong thread and report an error.

Ah, this brings back memories ;-)

BR,
Matra

Rick Strahl
October 19, 2012

# re: Creating STA COM compatible ASP.NET Applications

@Matej - Yes the proxying that occurs is there, but there are other issues that usually affect many STA implementations. Visual FoxPro's (and think Visual Basic's) STA implementation for example uses thread local storage to hold some global instance data and if that data gets mixed up on an MTA thread the COM instance is fried. This data sits outside of the proxy and this is what usually crashes the instances.

ASPCOMPAT from what I understand basically allocates special STA threads and ties them to specific threads that never change and COM objects are instantiated on these special threads that avoid this problem as the STA threads guarantee there's no cross talk anywhere on the thread. The entire ASP.NET handler request then runs on this STA request (but only the handler not modules that run before or after).

Jim Eggert
October 20, 2012

# re: Creating STA COM compatible ASP.NET Applications

You crossed out Asp.net Web Api in your list of non supporting tchnologies. Is STA not possible with Web Api or has the work not been attempted? If it is possible is the HttpMessageHandler the class that needs to be subclassed?

Thanks,

Jim

GroverL
November 21, 2012

# re: Creating STA COM compatible ASP.NET Applications

How about performance, dude?? for the various methods of doing this. Your old article tackled performance as well. My bet is the fastest way to have VFP interact with .NET (and also the web) is an HTTP Handler.

Rick Strahl
November 21, 2012

# re: Creating STA COM compatible ASP.NET Applications

@Grover - A handler is always going to be the fastest - in the end most of these implementations are handlers (MVC,WebForms,WebPages all are HTTP Handlers). If you write raw output from a handler it will be somewhat faster - in the end what the handler does determines the real performance.

Accessing STA components significantly slows ASP.NET in general as it has to create and manage separate threads from the regular MTA threads it uses to fire requests, but the overhead for that will be the same for each of the approaches.

For raw throughput performance I think the same rules will apply here as in my previous post on raw throughput performance, except that I'd bet the differences will be less defined as the STA access will add a bit of overhead. The previous article can be found here:

http://www.west-wind.com/weblog/posts/2012/Sep/04/ASPNET-Frameworks-and-Raw-Throughput-Performance

Dipal Mehta
February 12, 2013

# re: Creating STA COM compatible ASP.NET Applications

We've many VB6 components that are running in STA mode .
Now, we want to develop a Web API that consumes this components.

But, Web api runs in MTA mode and we can't have page in this project.

Also you strike web api in your post.
So, Is there any workaround to achieve same in our web api project.

Thanks in advance.

Rick Strahl
February 13, 2013

# re: Creating STA COM compatible ASP.NET Applications

@Dipal - I struck it out because I couldn't find info on how to do this. The problem is that Web API uses many hooks to activate operations and then runs almost everything it does asynchronously, so I suspect it's more complicated to get this set up.

I don't know of a way to do this at this point unfortunately. Maybe somebody else has a comment on where to look for this sort of operation, but I don't think it's possible because WebAPI uses a processing pipeline that has many entry points. All the other examples, have a single entry point so we can isolate the processing - not so with WebAPI.  

If it is possible I don't know how to do it...

julien
March 01, 2013

# re: Creating STA COM compatible ASP.NET Applications

Hi,
Thank you very much for your article!
I used your ASP.NET MVC code in my project and it seemed to have solved my problem. I haven't run it long enough to see if there are no side-effects. I won't on looking on the net and found this other very similar solution : http://forums.asp.net/post/2545364.aspx
What I don't get is that for instance in your ProcessRequest method, you throw an Exception when theozco is coding this.ProcessRequest(httpContext). What will be the differences then between his and your solution please?

Guido
December 11, 2013

# re: Creating STA COM compatible ASP.NET Applications

Hi Rick,

I tried to use your StaHttpHandler from asp.net 4.5. I get an error msg saying <%@ Page AspCompat="true" %> and <httpRuntime apartmentThreading="true" /> are not supported. I am requested to add <add key="aspnet:UseTaskFriendlySynchronizationContext" value="false" /> to web.config. (For me this is strange because the doc says false is the default value). If I set this app key then the StaHttpHandler works without problems but web sockets stop to work. (Because they do not like any compatibility settings). Do you have any ideas?

Kind regards,

Guido

BJan
March 17, 2016

# re: Creating STA COM compatible ASP.NET Applications

I am trying to share code between VB6 exe and a website. The original code is in VB6 so my idea is to convert all business logic to dll and share it between VB6 and asp.net. I was confused what to do with session/user specific code. I came across this blog, you are making ASP.NET to work as STA, don't you think it will kill the performance, the main idea behind multiple threads in ASP.NET?

Rick Strahl
March 19, 2016

# re: Creating STA COM compatible ASP.NET Applications

@BJan - yes you definitely should try to avoid COM interop, but when you need it, you need it!

When using STA/ASP.NET IIS sets aside a certain number of threads to run for the STA threadpool and those threads get dedicated to STA request handling. The rest of an application should be relatively unaffected - only those requests that use the STA components are immediately affected in performance.

All that is pretty much moot though - the STA overhead is minimal compared to actually making a COM method call including the COM marshalling and string type conversions etc.

Bottom line - don't use STA unless you have to, but when you do need to use it these mechanisms at least let you do it.

BJan
March 20, 2016

# re: Creating STA COM compatible ASP.NET Applications

Thank you very much. Hmm, the actual performance bottleneck is COM call and marshalling. It sounds like converting an EXE project to COM Dll (to be used in .net) is not going to be a good approach because there will be multiple COM calls to perform a single operation and there will be hundreds of end users. I must rewrite the code in .net but at the same time i am going to nullify the reliability of the existing code, right?

Rick Strahl
March 20, 2016

# re: Creating STA COM compatible ASP.NET Applications

@BJan - you can use EXE COM servers, but there's a lot more overhead in doing so unless you cache the DCOM servers and use them on their own dedicated threads. Many years ago I built a framework called Web Connection for Visual FoxPro servers that uses such a COM thread pool which works pretty well. It's not anywhere approaching native .NET performance but we're able to wring out about 300 req/sec out of this setup. It's workable but it requires a bit of work to build a good thread manager, which might just be better spent re-writing your code. In my case it's a generic product that was rebuilt, and surprisingly the performance of the .NET version was actually considerable (2x!) faster than the native W32 ISAPI implementation that preceeded it.

FSmall
November 07, 2016

# re: Creating STA COM compatible ASP.NET Applications

Rick,

Since it appears Web API is not supported for calling a FoxPro MTDLL, could something like the following work?

var th = new Thread(ExecuteCreateFiles);
th.SetApartmentState(ApartmentState.STA);
th.Start();
th.Join();
 
//...
 
private void ExecuteCreateFiles()
{
  dynamic fox = ComHelper.CreateObject("myFoxProCOM.FindInfo");
  dynamic abc = fox.CreateFiles();
}


Essentially this is creating a new thread, setting it to STA and executing the FoxPro COM. I don't need anything back from the COM. Perhaps a true/false that it completed would help. (The Join tells the process to wait until the thread is finished processing -- I need the files to exist before I can continue.) What drawbacks are there with this methodology? What kind of additional load is being created on the server?

I have also thought about creating a .NET service (that can be run in STA mode) and calling it from my Web API project. Do you think this would create less of a load then my previous idea?

One last question. All your articles that I have read speak about the MTDLL's. Does STDLL's have the same issues in .NET as MTDLL's? I have been using a STDLL in a classic ASP website for about 10 years with no known issues.

Thanks for the help!

Frank S.

Luke B
April 04, 2019

# re: Creating STA COM compatible ASP.NET Applications

Hi,

I recently stumbled across your article which helped enormously with a legacy com module we're using.

The team wish to move to .Net Core. It appears accessing Com objects is possible however is there a way of creating a STA within .net Core?

https://weblog.west-wind.com/posts/2019/Jan/22/COM-Object-Access-and-dynamic-in-NET-Core-2x

Thanks

Luke


Rick Strahl
April 04, 2019

# re: Creating STA COM compatible ASP.NET Applications

@Luke - not directly. This is the same as WebAPi (which wasn't covered by this article) and the problem is that ASP.NET Core is MTA and async all the way down the pipeline. A request may jump threads multiple times as it passes through the pipeline.

The only way that you can call STA code in code is too offload it to a pinned STA thread and wait for completion. I've done this with a separate thread pool of a ported ASP.NET classic framework, but there's a lot of extra plumbing to make that work. It's not as easy as just marking a request STA and go and there's no standard handler that provides for that.


West Wind  © Rick Strahl, West Wind Technologies, 2005 - 2024