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.