ASP.NET Web Services and STA COM Objects

Posted on March 4th, 2006.

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: , , ,

Make a Comment

12 Responses to “ASP.NET Web Services and STA COM Objects”

RSS Feed for Eyal’s Posts Comments RSS Feed

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. 🙂

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

Fixed. Thanks.

Eyal Post
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

Where's The Comment Form?

eXTReMe Tracker