Rick Strahl's Weblog  

Wind, waves, code and everything in between...
.NET • C# • Markdown • WPF • All Things Web
Contact   •   Articles   •   Products   •   Support   •   Advertise
Sponsored by:
West Wind WebSurge - Rest Client and Http Load Testing for Windows

Hosting a WCF Service in a non-.NET Client


:P
On this page:

I'm still screwing around West Wind Web Connection and trying out a quick proof of concept tonight and thought I'd share a few steps on how to host a Windows Communication Foundation (WCF) Service in a COM client like Visual FoxPro. The scenario I'm playing around with is to provide another alternate messaging mechanism for West Wind Web Connection that uses WCF to pass messages between the West Wind Web Connection Connector (in this case an ASP.NET HttpModule) and a Visual FoxPro object.

Yeah I don't have a life - this is what I call fun on Friday night <bg>... but I disgress.

So the scenario that I'm building looks like this: The West Wind Web Connection HttpHandler receives inbound request and then passes messages to the Visual FoxPro server. Currently the server supports COM based messaging in several modes (EXE, DLL and SingleMode) as well as file based processing using message files which is used for debugging purposes. The process is completely transparent and the messaging mechanism is basically a drop in behavior both on the HttpHandler side and in the West Wind Web Connection server that simply sets a switch to determine how messages are passed. The advantage of COM over is basically that it's self-activating - you can instantiate a COM object keep a reference around to it and fire requests. Any alternate approaches require some life cycle management to ensure that the server is up and running and stays up and running.

So let's look at creating a WCF service that can be consumed and talk back to a Visual FoxPro application shall we? Note this discussion is a basic overview of creating WCF clients and servers in general and the interop scenario can be applied to any COM capable non-.NET client. So don't let the Visual FoxPro here scare you off.

WCF - Self Hosting

WCF supports the concept of self-hosting which allows host of a WCF service inside of any process that can run .NET. Visual FoxPro doesn't natively run .NET of course but via COM interop (or by hosting the .NET runtime in VFP) you can still take advantage of WCF in VFP. What I'm interested in here is a two things:

  • Being able to host the service
  • And being able to have VFP service incoming requests

The first is obvious, but the second maybe isn't. WCF self-hosting basically runs .NET code and it does so by running in a separate .NET AppDomain. WCF essentially sets up a full hosting environment around your service implementation (a class + interface) that enables your application to listen to inbound service requests. By this mechanism you get full support for all the protocols that WCF supports: Http,Ws*Http, TcpIp, NamedPipes, Queues, Peer2Peer etc. and gives your application the ability to basically host any of these protocols with your service. WCF handles all the dirty details - handling the protocols, handling the multi-threading (more on this in a minute!), security and all the other semantics associated with the service and its protocols. If you think about it it basically gives you a server in pretty easy to use API which requires very little code to configure (especially if you can share ServiceContracts between both sides of the connection as I'll do here).

So the idea of this service will be that it will be hosted inside of Visual FoxPro and implement basically the same signature that the West Wind Web Connection server implements (which is two simple methods GetProcessId and ProcessHit). What I'll do is implement a service contract interface and implement a class that includes these two methods. I can then use this class to host in WCF and that will allow firing the service code. That gets us a service running .NET code.

But there's still the issue of how do we fire FoxPro code. The way to accomplish this is to create the service and add a property that holds an instance of an object that you want code fired on. So in my case I created a class that has WebConnectionServer property and that property is set during initialization of the service instance. That instance is then used to create the service host effectively making that instance available to the .NET code to fire code on.

Enough talking - here's what the simple Service implementation looks like in C# code:

using System;
using System.ServiceModel;
using Westwind.Tools;
using System.Text;

namespace WebConnectionWcf
{
    [ServiceContract]
    public interface IWebConnectionService
    {
        [OperationContract]
        byte[] ProcessHit(string RequestData);

        [OperationContract]
        int GetProcessId(int Instance);
    }

    [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
    public class WebConnectionService : IWebConnectionService
    {
    #region IWebConnectionChannel Members

        /// <summary>
        /// The West Wind Web Connection Server
        /// </summary>
        public object WebConnectionServer
        {
            get { return _WebConnectionServer; }
            set { _WebConnectionServer = value; }
        }
        private object _WebConnectionServer = null;


        public byte[]  ProcessHit(string RequestData)
        {
            return Encoding.Default.GetBytes( wwUtils.CallMethodCom(this.WebConnectionServer, "ProcessHit", RequestData) as string );
            //return string.Format("<b>Hello World {0}</b>", DateTime.Now));
        }

        public int  GetProcessId(int Instance)
        {
            return 1;
        }

        #endregion
   }
}

This is standard WCF fare - a service contract implementation and an actual implementation of the interface on a concrete class. There are two methods which map the signature of the methods I want to call back on my FoxPro server. Notice also that I have a WebConnectionServer property which is to receive an instance of the FoxPro object that is responsible for processing when a service requests comes in. You'll notice that the ProcessHit method use this server instance to call through to the ProcessHit method which is in effect a Visual FoxPro object.

I mentioned that threading is important earlier. Notice the [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]  attribute at the top of the class. What this says is that this server will be considered a single mode server and process only one request at a time, rather than using the default of handling multiple requests simultaneously. This is important to consider IF you are expecting to have Visual FoxPro code respond to inbound requests. If you only have .NET code running multiple requests, then this is not necessary but VFP being single threaded cannot handle multiple simultaneous requests. We'll need to do some special configuration handling to let WCF know that we want Singleton mode in the code below.

The service runs inside of the WCF created AppDomain separate from any other .NET code so you don't talk to this code directly.

The next step is to activate the service and make it externally accessible. In this case I will use a single assembly to hold both the Service and the Host and share the ServiceContact with both the server and the client that calls the service. In order to host the service we need to create a service host and attach a binding that tells WCF how to publish the service:

[ComVisible(true)] 
[ProgId("WebConnectionWcf.WebConnectionWcfHost")]
public class WebConnectionWcfHost : IDisposable
{
    ServiceHost Host = null;
    object WebConnectionServer = null;
    
    public string ErrorMessage
    {
        get { return _ErrorMessage; }
        set { _ErrorMessage = value; }
    }
    private string _ErrorMessage = "";

    /// <summary>
    /// Starts the WCF service with a singleton instance
    /// </summary>
    /// <param name="ServiceId"></param>
    /// <returns></returns>
    public bool Start(object WebConnectionServer, string ServiceUri)
    {
        // *** if no instance is passed create a COM instance instead
        if (WebConnectionServer == null)
        {
            WebConnectionServer = wwUtils.CreateComInstance("wcDemo.wcDemoServer");
            wwUtils.CallMethodCom(WebConnectionServer, "GetProcessId", 1);
        }

        WebConnectionService Service = new WebConnectionService();
        this.WebConnectionServer = WebConnectionServer;
        Service.WebConnectionServer = WebConnectionServer;             

        try
        {
            Binding Binding = null;
            if (ServiceUri.StartsWith("net.tcp://"))
                Binding = new NetTcpBinding();
            else if (ServiceUri.StartsWith("net.pipe://"))
                Binding = new NetNamedPipeBinding();
            else if (ServiceUri.StartsWith("http://"))
                Binding = new BasicHttpBinding();

            Host = new ServiceHost(Service, new Uri(ServiceUri));
            this.Host.AddServiceEndpoint(typeof(IWebConnectionService), Binding, ServiceUri);                
            Host.Open();
        }
        catch (Exception ex)
        {
            this.ErrorMessage = ex.Message;
            return false;
        }

        return true;
    }

    public bool Stop()
    {
        if (this.WebConnectionServer != null)
        {
            Marshal.FinalReleaseComObject(this.WebConnectionServer);
            this.WebConnectionServer = null;
        }

        if (this.Host != null)
        {
            Host.Close();
            Host = null;
        }
        return true;
    }

    #region IDisposable Members

    public void Dispose()
    {
        this.Stop();
    }

    #endregion
}

Note that this object is ComVisible so it can be called from Visual FoxPro using .NET COM Interop. The Assembly needs to be registered with RegAsm (or Visual Studio will do it for you with 'Register for Com Interop').

The key method is Start() which starts the service. The way this service is created is a little special because it is specifically creating a singleton instance. Normally WCF receives a type rather than a pre-loaded instance when creating the ServiceHost. When passing an instance like this:

Host = new ServiceHost(Service, new Uri(ServiceUri));

where Service is an instance of the Service class WCF creates a singleton service with only a single instance servicing requests. For running against Visual FoxPro code which is single threaded this exactly what the doctor ordered.

Note that I can pass in an object instance to my FoxPro object - in this case the West Wind Web Connection server. This is  a plain FoxPro object that FoxPro fixes up into a COM object on the fly. Once passed this object is then available to the service since we were able to precreate the instance that the ServiceHost uses for processing and so the FoxPro object is available to each of the service calls. Note that this only works with Singleton services like this because in normal services there's no easy way to attach a property/object and have it set during construction because WCF handles this behind the scenes. But again - this is exactly what's needed here for talking back to a FoxPro object!

Back at the farm in FoxPro

Ok all the above is .NET code - what about VFP? Well with all this in place you can probably guess that the FoxPro code to run the WCF service is pretty light. Here's the code to start listening with the service:

*** Load West Wind Web Connection libs so WWWC load Server
DO wconnect
SET PROCEDURE TO wcDemomain ADDIT

*** Load my West Wind Web Connection server
loServer = CREATEOBJECT("wcDemo.wcDemoServer")

*** Load the WCF Service Host class
loHost = CREATEOBJECT("WebConnectionWcf.WebConnectionWcfHost")

*IF (!loHost.Start(loServer,"net.pipe://rasvista/WebConnectionDemo"))
*IF (!loHost.Start(loServer,"http://rasvista/WebConnectionDemo"))

*** Start the server 
IF (!loHost.Start(loServer,"net.tcp://localhost:8001/WebConnectionDemo"))
   ? loHost.ErrorMessage
ENDIF   

*** We are no listening - you'll need something to keep the 
*** loHost instance alive 
WAIT WINDOW "Running Host... press any key to stop"

loHost.Stop()

RETURN

And that's it. Note that you'll need to hang on to the host reference so that the server stays alive. Stick it onto a form or attach it to a PUBLIC variable that sticks around. As soon as you release the host object the .NET object will also release.

Once the service is running you can call it from any client.

To make things easier I created a client wrapper for the service, so it's going to be easy to call this service from .NET (which is what I need to do) but we can also expose it to be called from Visual FoxPro - so you can have two Visual FoxPro instances communicate with each other via WCF. Here's the client wrapper that handles calling the two server methods:

using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using System.ServiceModel;
using System.ServiceModel.Channels;
using Westwind.Tools;

namespace WebConnectionWcf
{
    [ComVisible(true)]
    [ProgId("WebConnectionWcf.WebConnectionWcfClient")]
    public class WebConnectionWcfClient
    {
        private string endPointUri = "net.pipe://localhost/WebConnectionDemo";

        /// <summary>
        /// Error message returned on a failure result from method calls
        /// </summary>
        public string ErrorMessage
        {
            get { return _ErrorMessage; }
            set { _ErrorMessage = value; }
        }
        private string _ErrorMessage = "";

        /// <summary>
        /// The internal Proxy instance used for this 
        /// </summary>
        protected IWebConnectionService Proxy
        {
            get
            {
                if (_Proxy == null)
                {
                    try
                    {
                        EndpointAddress ep = new EndpointAddress(this.endPointUri);

                        Binding Binding = null;
                        if (this.endPointUri.StartsWith("net.tcp://"))
                            Binding = new NetTcpBinding();
                        else if (this.endPointUri.StartsWith("net.pipe://"))
                            Binding = new NetNamedPipeBinding();
                        else if (this.endPointUri.StartsWith("http://"))
                            Binding = new BasicHttpBinding();

                        _Proxy = ChannelFactory<IWebConnectionService>.CreateChannel(Binding, ep);
                    }
                    catch (Exception ex)
                    {
                        this.ErrorMessage = ex.Message;
                    }
                }
                return _Proxy;
            }
            set { _Proxy = value;}
        }
        private IWebConnectionService _Proxy = null;


        /// <summary>
        /// Allow client to specify the endpoint - can't do
        /// constructor over COM so we need a method here
        /// </summary>
        /// <param name="EndPointUri"></param>
        /// <returns></returns>
        public bool SetEndPoint(string EndPointUri)
        {
            this.endPointUri = EndPointUri;
            return true;
        }

        /// <summary>
        /// Process a West Wind Web Connection Server request on the server
        /// </summary>
        /// <param name="RequestData"></param>
        /// <returns></returns>
        public string ProcessHit(string RequestData)
        {
            string Output = null;

            if (this.Proxy == null)
                return null;

            try
            {

                Output = Encoding.Default.GetString(Proxy.ProcessHit("Query_String=wwdemo~TestPage") as byte[]);
            }
            catch (Exception ex)
            {
                this.ErrorMessage = ex.Message;
            }

            return Output;
        }

        /// <summary>
        /// Gets the ProcessID from the West Wind Web Connection Server
        /// </summary>
        /// <param name="InstanceId"></param>
        /// <returns></returns>
        public int GetProcessId(int InstanceId)
        {
            return Proxy.GetProcessId(InstanceId);
        }

        /// <summary>
        /// Cleanup the Proxy if not in use
        /// </summary>
        public void ReleaseProxy()
        {
            this.Proxy = null;
        }
    }
}

There's a bit of extra gunk here to automatically load the proxy transparently and handle the various protocols transparently that's why the class is a bit wordy. But really all it is is a few lines of code that create the proxy (if isn't already loaded) and then fire the method.

The client class is also exposed to COM so you can now instantiate it from Visual FoxPro as well:

LOCAL loClient as WebConnectionWcf.WebConnectionWcfClient
loClient = CREATEOBJECT("WebConnectionWcf.WebConnectionWcfClient")

loClient.SetEndPoint("net.tcp://localhost:8001/WebConnectionDemo")
? loClient.GetProcessId(1)
? loClient.ProcessHit("Query_String=wwDemo~TestPage")

RETURN

To make this work start up two instances of VFP. Run the the first block I showed to run the server. Start up another and then run this code here to fire the client.

You might be tempted to host both the client and the server in the same VFP instance - WCF supports this having the client and server both in the same process. But remember that VFP is single threaded so it can't walk and chew gum at the same time <s>. So, the call to ProcessHit which is pure .NET code works just fine. But the call to ProcessHit which fires back into FoxPro to execute the server code will lock up because VFP is busy waiting on the Service call to complete - and you'll lock up VFP. So while WCF can host both client and server it only works if you are not calling back into VFP code. No big deal - this surely not a common scenario.

This is pretty sweet. Keep in mind that you can now switch between protocols simply by specifying a different protocol. For local machine traffic net.pipe:// tends to be the most efficient. For cross machine processing Tcp/IP is fastest and Http works best for firewall situations. Note that using the Http protocol can co-exist with IIS on a local machine (but I think only on Windows Server 2003, Vista and Longhorn server where http.sys is used).

My next step will be to figure out lifetime management - but I'll leave this for another day.

Why?

So why all of this farting around with WCF here when COM already works? Well this was an excercise for me to see how I can use WCF in a scenario like this for cross communication. I'm thinking ahead for West Wind Web Connection as well in a few years when LongHorn server is around and we're running on 64 bit boxes and when we want to avoid running any 32 bit code in the Web server. 32 bit isn't likely to go away but getting it out of the Web Server at least is probably a good idea as time moves on. So a lot of the stuff I'm doing with the managed module is geared towards that scenario.

And heck what better way to get familiar with the technology (WPF) and see how well it works under load than with a tool that I can load up heavily? <s>

Referrence

Code from the Post  (note the code uses the West Wind Web Connection COM object as shown)

Intro to VFP + .NET COM Interop  

Calling and hosting FoxPro Web Services through .NET

Posted in .NET  COM  FoxPro  Web Connection  WPF  

The Voices of Reason


 

Hank Fay
June 20, 2007

# re: Hosting a WCF Service in a non-.NET Client

Great stuff, as always. Thanks for putting it out for those of us for whom reading and understanding the article would be a productive Friday evening. &lt;g&gt;

Craig Boyd
June 21, 2007

# re: Hosting a WCF Service in a non-.NET Client

Nice! I'm going to go mess around with this. Thanks for taking the time.

West Wind  © Rick Strahl, West Wind Technologies, 2005 - 2024