This information isn’t exactly secret, but it took a bit of googling around to piece the answer together, so I wanted to share it in a more condensed form.
We are in the process of migrating some legacy ASP code over to .NET, and we are doing it in stages. As an intermediate step, we are switching the back end of the application to talk to newer .NET code that maintains a very similar interface. We are routing the communication to the new back end using web services.
It’s not hard to make a web service call from ASP. There is a good starting point reference out on the Code Project, Call XML Web service from ASP, and it will be the top hit if you Google something obvious like “call web service from ASP”. If you try the code out, it will work well enough, but there are a couple of serious gotchas that it doesn’t address.
ASP.Net Restrictions on POST to localhost
The first major issue is that the ASP code is using a POST protocol to pass data to the web service. Most engineers do their development on a single box, so for multi-tiered applications, they are hosting both the web and application tiers on a single server. When it is actually deployed, the code will be separated out with web logic on one machine and application logic on another, but it makes development and debugging much simpler to do it on a single box.
In theory it shouldn’t matter, but it turns out that ASP.NET treats web service calls coming from a local box as different from calls from a remote box. Code that works fine on a developer’s box will fail when it is actually deployed in a real test environment with truly separate tiers. The issue is that by default, ASP.NET will only allow web service calls to receive SOAP requests. A POST of the same data is automatically rejected, unless it is originating from the local machine, which is presumed to be trusted. As a result, POST from the local box works just fine, but it starts failing as soon as it is taken to another environment. It fails with an unhelpful message about “Request format is unrecognized for URL…”.
Microsoft outlines more details here, but the gist of it is that HttpSoap and HttpPostLocalhost are on by default, while HttpGet and HttpPost are off by default. These can be re-enabled in the web.config file. Since our application servers are in a restricted zone that can only be contacted from specific internal machines, we can enable this option.
XMLHTTP vs. ServerXMLHttp
The need to address the HttpPost is not the only problem with the example from the Code Project. One very nasty one is that they are making their request using XMLHTTP. This is a nice, useful object for making web service calls – if you are running from a browser. On a server-side component, it’s a big problem.
The problem is that HTTP has all kinds of restrictions baked into it, like a default limit of two connections to a server object at a time. XMLHTTP inherits those restrictions, which means that if more than two requests are trying to call webservices concurrently using this object, all other requests will hang until a connection is freed up. I rigged up a test page where I had a webservice that would just wait for some specified chunk of time, like 30 seconds. I started up two pages hitting it in two separate browsers, and then opened a third one that was supposed to wait just 3 seconds. Sure enough, the third page hung for 33 seconds – the 30 seconds it spent waiting for one of the other processes to finish, and then 3 seconds for its own execution.
The correct approach is to use ServerXMLHttp. This is very similar to XMLHTTP, but it is designed to be executed in server contexts, and it doesn’t suffer from the two request limitation. I tried the same test as above but using ServerXMLHTTP, and this time it worked as expected; the 3 second request completed in 3 seconds, despite the fact that the two other 30 second requests were executing simultaneously.
One last problem with the Code Project example is the way it passes variables to the web service. They show a simple calculator example, and so they are able post data in a very simple “parameter=value” structure. That works fine for simple data like numbers, but if you need to pass rich text data in an argument, it will start failing. The values need to be URL encoded before they are sent across using Server.URLEncode.