ASP.NET Web Services and STA COM Objects
Eran’s post (where he mentions a hotfix for ASP.NET with regard to STA COM objects) reminded me of a problem we had with web services and STA COM objects (We worked for the same company..).
If you have no idea what STA COM objects are, You didn’t miss anything and I really envy you. You can jump to the end of the article where there’s a line about you.
ASP.NET web services do not support the aspcompat attribute which is available for asp.net web pages. This means calls to STA COM objects are made from an MTA thread and always incur a thread switch to the STA thread.
There are a few solutions and guidelines but they don’t compare to the convenience provided by the aspcompat attribute and they generally suffer from performance problems.
Since web services support a “stateful” mode (see EnableSession in the WebMethod attribute) which was nicely aligned with the way our COM objects where designed, we needed a way to make sure that whenever a web method is called from the same session, it will run on the same thread and that this thread will be a STA thread. This is the same behavior aspcompat pages have and we needed a way to duplicate that.
The solution I came up with was actually very simple – But it took quite a while to find.
To understand the solution, you need to realize that an asmx (web service) page is handled just like any other asp.net page. ASP.NET reads the httpHandlers section in the web.config file and finds the handler for asmx pages. What I did was to “hijack” asmx handling and redirect it to my own System.Web.UI.Page derived object. Since this object is derived from Page, it has the infrastructure for the aspcompat attribute. When that page was loaded, I simply loaded the real asmx page and handed the request to it. Here’s some source code:
..
using System.Web.Services.Protocols;
using System.Web.SessionState;
..
public class STAWebServiceHandler: System.Web.UI.Page, IHttpAsyncHandler,IRequiresSessionState
{
 //This is the real handler factory for asmx pages
 private static WebServiceHandlerFactory defaultFactory= new WebServiceHandlerFactory();
 override protected void OnInit(EventArgs e)
 {
   //Get the real handler and ask it to process the request
   IHttpHandler Handler=defaultFactory.GetHandler(Context,Context.
     Request.HttpMethod,Context.Request.RawUrl,Context.Request.PhysicalPath);
  Â
   if (Handler!=null)
     Handler.ProcessRequest(Context); }
   //This is where the magic happens. Our BeginProcessRequest
   //and EndProcessRequest calls the inherited
   //AspCompatBeginProcessRequest and AspCompatEndProcessRequest
   //to process this page in an “aspcompat” way.
   public IAsyncResult BeginProcessRequest(HttpContext context,
            AsyncCallback cb, object extraData)
   {
       return AspCompatBeginProcessRequest(context,cb,extraData);
   }
  Â
   public void EndProcessRequest(IAsyncResult result)
   {
     AspCompatEndProcessRequest(result);
    }Â
  }
}
You also need to make sure your web.config has an entry in the httpHandlers section that points to this class.
I hope that no one will actually need to use this code. I’m really looking forward to the days where STA COM objects will be to programmers what COBOL is to me..:) (No offense, COBOL guys..)
Technorati: STA, COM, ASP.NET, Web Services
12 Responses to “ASP.NET Web Services and STA COM Objects”
Steve McKinney
June 28th, 2006
Hey,
Any idea how to achieve this in .NET remoting on HTTP channel? I have the same situation on my application servers. I create and cache STA objects in a global pool. Next requests get queued on some STA thread.
Great article BTW. ..
Thanks
PR
October 19th, 2006
Never mind..I found it..
You can also use this with remoting.. You need to host your remoting server in IIS and instead of using WebServiceHandlerFactory, use RemotingHandlerFactory..
You can speicify this handler for .soap extensions in your web.config within section..
PR
October 20th, 2006
Hi, don’t know if you can help, but I’m dying with a certain STA component that requires AspCompat=true (in an aspx page). The whole webserver just blocks until the call completes.. and I can’t find a way around it. Specifically this is MS DRM-s WMRMObjs.Reheader object and within that, the Dowload function. Any ideas of how to circumvent this, or get it to unblock? Even if I cancel the download, I’m left with about 30s of deadtime until it finishes something internally :(( This is a real project killer at the moment 🙁
My email is uramisten at hotmail dot com
Cheers
Ati Rosselet
March 5th, 2007
Got this working, but there is a typo:
The web services handler should be named WSHFactory instead of defaultFactory
Good work. Thanks
Sam
May 30th, 2007
I’ve implemented this handler, and have found that execution disappears into the COM call and doesn’t reappear for a very long time (long enough to cause the requesting app to give up with a webservice timeout). The COM object it’s calling is reasonably performant. Any ideas on what could be causing this?
Ben
June 13th, 2007
Gr8. This idea is lifesaver although speed is not good but it is not bad too. Thank you very much.
Prafull Patil
October 12th, 2007
I found that I had to manually assign an arbitrary page id in the OnInit method, otherwise I got the following exception in EndProcessRequest:
Multiple controls with the same ID ‘__Page’ were found. Trace requires that controls have unique IDs.
[HttpException (0x80004005): Multiple controls with the same ID ‘__Page’ were found. Trace requires that controls have unique IDs.]
System.Web.TraceContext.AddNewControl(String id, String parentId, String type, Int32 viewStateSize, Int32 controlStateSize) +475
System.Web.UI.Control.BuildProfileTree(String parentId, Boolean calcViewState) +359
System.Web.UI.Page.BuildPageProfileTree(Boolean enableViewState) +39
System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint) +6341
Danny
April 14th, 2008
I integrate this code to a project and it works fine when I’m debugging it under Visual studio development server, but when I move the code to a real IIS server 6.0, which by the way is in the same machine, I can not attach the ocx, myocx.attach() return -1. I know this a very general explanation, but I don’t know where to start. Any insight on this.
roncansan
July 23rd, 2008
When I upgraded my site to 2008, I had to change the code as it gave a … is not a valid virtual path error.
The solution I found was to strip the querystring from the raw url:
override protected void OnInit(EventArgs e)
{
string virtualPath = Context.Request.RawUrl;
// strip the querystring off
if (virtualPath.IndexOf(‘?’) > 0)
{
virtualPath = virtualPath.Substring(0, virtualPath.IndexOf(‘?’));
}
//Get the real handler and ask it to process the request
IHttpHandler Handler = defaultFactory.GetHandler(
Context
,Context.Request.HttpMethod
, virtualPath
,Context.Request.PhysicalPath);
if (Handler!=null)
Handler.ProcessRequest(Context);
}
Sam
August 27th, 2008
ahhh – made a mistake above … can just use Context.Request.Path to get the actual virtual path … do no rigmarole of stripping the querystring
Sam
August 27th, 2008
Hey, thanks — that’s a lifesaver! I’d like to kiss those STA COM objects goodbye too, but it’s not to be, not yet anyway. 🙂