Using web services locally
Aug 2, 2004 Update: Download the sample project for this post.
I am currently working on a system that utilizes asmx-based SOAP methods for inter-server communication. While it needs to work in a distributed environment, it also have needs to be installed on a single server. In this configuration, the web services could be installed on the server under different virtual directories. However, this adds overhead and addition setup requirements to the web application. While I realize that remoting already support switching between local and remote objects, I need to do it in an asmx world.
To solve this problem, I decided to build a proxy that would intercept the SOAP calls and execute the object directly. This allows all of my existing code to remain the same in both environments.
First, I utilize a factory method for creating my web service objects. I've found this is always a good idea since you can initialize parameters such as timeouts from a single location. In this case, the factory uses my new class WSProxy to support local object calls. The Run.SOAP.Locally configuration setting makes switching back and forth easy.
Since System.Web.Services.Protocols.HttpWebClientProtocol is a decendent of MarshalByRef, we can create a proxy that intercepts the calls and directs them to the wstest.Service1 class. Note: the assembly for the web service must be referenced by our web application to support local calls.
private wstestsvc.Service1 wstestFactory() { bool remote=bool.Parse(
System.Configuration.ConfigurationSettings.AppSettings["Run.SOAP.Locally"]); wstestsvc.Service1 wstestsvc=new wstestsvc.Service1(); if (!remote) wstestsvc=(wstestsvc.Service1) WSProxy.CreateProxy(wstestsvc,new wstest.Service1()); return wstestsvc; }
The following snippet is an example of calling the typical HelloWorld() method on our object.
private void button1_Click(object sender, System.EventArgs e) { wstestsvc.Service1 wstestsvc=wstestFactory(); label1.Text=wstestsvc.HelloWorld(); }
Now for the code that does all of the work! WSProxy is an implementation of a RealProxy. For more information about proxies, there's a good article on CodeProject. Whenever a method is called on the web service class, the WSProxy.Invoke method is passed a message with the method name and arguments. Since web service client stubs are always decended off of System.Web.Services.Protocols.HttpWebClientProtocol, we'll just capture and redirect the methods from the decendent type with reflection.
using System; using System.Reflection; using System.Runtime.Remoting; using System.Runtime.Remoting.Proxies; using System.Web.Services.Protocols;
using System.Runtime.Remoting.Messaging;
namespace wstest_client { public class WSProxy : RealProxy { HttpWebClientProtocol target; object targetObj; Type targetType; public WSProxy(HttpWebClientProtocol webclient, object targetObj) : base(webclient.GetType()) { target=webclient; targetType=targetObj.GetType(); this.targetObj=targetObj; } public static object CreateProxy(HttpWebClientProtocol webclient, object targetObj) { return new WSProxy(webclient, targetObj).GetTransparentProxy(); } public override IMessage Invoke(IMessage message) { // Convert to a MethodCallMessage IMethodCallMessage methodMessage = new MethodCallMessageWrapper((IMethodCallMessage)message); MethodBase method=methodMessage.MethodBase; object[] parameters=methodMessage.Args; object returnValue; // We'll redirect the web service method (which will be declared in the decendent type) if (method.DeclaringType==target.GetType()) method=targetType.GetMethod(method.Name); // Execute the method returnValue=method.Invoke(targetObj, parameters); // Create the return message (ReturnMessage) ReturnMessage returnMessage =
new ReturnMessage(returnValue, methodMessage.Args,
methodMessage.ArgCount, methodMessage.LogicalCallContext, methodMessage); return returnMessage; } } }
This seems to be a good solution to my problem and would provide other opprotunities for extensions. Is anyone else running into a similar situation? I can post a demo project if anyone is interesting.