Sunday, September 02, 2012

A Generic WCF Proxy Class

A couple of years ago, when I first started using WCF, it seemed like a real pain in the you-know-what to add Service References to my app in order to consume WCF Services. I wanted a base Proxy class that I could extend to be used for any kind of ServiceContract. The key to writing this class was the use of generics Here’s what I came up with, after much trial and error (be sure you have using System.ServiceModel and using System.ServiceModel.Channels):

public abstract class ProxyBase<T> where T : class
{
    #region Declarations
    public ChannelFactory<T> Factory = null;
    public T proxy { get; set; }
    public string EndpointName { get; set; }
    private string m_Certificate;
    public string Certificate
    {
        get { return this.m_Certificate; }
        set
        {
            try
            {
                this.m_Certificate = value;
                if (this.m_Certificate.IsNotNullOrEmpty() && Factory != null)
                {
                    EndpointIdentity Identity = EndpointIdentity.CreateDnsIdentity(this.m_Certificate);
                    if (Identity == null)
                        Console.WriteLine("Identity Is Null");
                    else
                    {
                        Uri AddressUri = Factory.Endpoint.Address.Uri;
                        Factory.Endpoint.Address = new EndpointAddress(AddressUri, Identity);

                        // not sure, but probably have to remove old EventHandler before recreating the 
                        // proxy and recreating the Channel.
                        ((ICommunicationObject)proxy).Faulted -= new EventHandler(ProxyBase_Faulted);
                        proxy = Factory.CreateChannel();
                        ((ICommunicationObject)proxy).Faulted += new EventHandler(ProxyBase_Faulted);
                    }
                }
            }
            catch (Exception ex)
            {
                Factory = null;
                proxy = null;
            }
        }
    }
    #endregion

    #region Constructors
    public ProxyBase() { }
    public ProxyBase(string endpoint)
    {
        // not the best error-handling, but good enough for now
        // TODO - make error-handling better
        //        Already handling faulted channels, but now need to do something for bad addresses?
        //        Because I *think* a bad address will cause an exception in the creation of the Factory.
        try
        {
            this.EndpointName = endpoint;
            Factory = new ChannelFactory<T>(endpoint);
            proxy = Factory.CreateChannel();
            ((ICommunicationObject)proxy).Faulted += new EventHandler(ProxyBase_Faulted);
        }
        catch (Exception ex)
        {
            Console.WriteLine("Error in Proxy for {0}, Endpoint: {1}\r\n{2)", typeof(T).Name, endpoint, ex.Message);
            Factory = null;
            proxy = null;
        }
    }
    public ProxyBase(string endpoint, string address)
    {
        // not the best error-handling, but good enough for now
        // TODO - make error-handling better
        //        Already handling faulted channels, but now need to do something for bad addresses?
        //        Because I *think* a bad address will cause an exception in the creation of the Factory.
        try
        {
            this.EndpointName = endpoint;
            Factory = new ChannelFactory<T>(endpoint);
            Factory.Endpoint.Address = new EndpointAddress(address);
            proxy = Factory.CreateChannel();
            ((ICommunicationObject)proxy).Faulted += new EventHandler(ProxyBase_Faulted);
        }
        catch (Exception ex)
        {
            Console.WriteLine("Proxy for {0}, Endpoint: {1}, Address: {2}\r\n{3}", typeof(T).Name, endpoint, address, ex.Message);
            Factory = null;
            proxy = null;
        }
    }
    #endregion

    #region Methods
    public void Close()
    {
        if (proxy != null)
            ((IChannel)proxy).Close();
        if (Factory != null)
            Factory.Close();
    }
    #endregion

    #region Events
    private void ProxyBase_Faulted(object sender, EventArgs e)
    {
        Console.WriteLine("Proxy for {0}\r\n{1}", typeof(T).Name, ((IChannel)proxy).State.ToString());
        ((ICommunicationObject)sender).Abort();
        proxy = Factory.CreateChannel();
    }
    #endregion
}

Seems pretty simple. Notice that it’s an abstract class. You must sub-class it in order to use it. OK, so how do we construct the sub-class? Very simple also. First, you must have a few Interfaces defined, as you would normally have for any WCF service. Here is what I’m going to use for my example:

[ServiceContract(Namespace = "http://MyCompany")]
public interface IMessageService
{
    [OperationContract(IsOneWay = true)]
    void ProcessMessage(IMessage message);
}

[ServiceContract(Namespace = "http://MyCompany")]
public interface ISynchronousMessageService
{
    [OperationContract]
    bool ProcessMessage(IMessage message);
}

public interface IMessage
{
    string MessageToSend { get; set; }
    string MessageReceived { get; set; }
}
public class MyMessage : IMessage
{
    public string MessageToSend { get; set; }
    public string MessageReceived { get; set; }
}

All WCF Services need to have a ServiceContract and OperationContract, so there they are. I’ve also got an interface defined for the Messages I’m going to send, plus a MyMessage class that implements that IMessage interface.

Next, here is what my sub-classed Proxies might look like:

public class SynchronousProxy : ProxyBase<ISynchronousMessageService>, ISynchronousMessageService, IDisposable
{
    #region Constructors
    public SynchronousProxy(string endpoint) : base(endpoint) { }
    public SynchronousProxy(string endpoint, string address) : base(endpoint, address) { }
    #endregion

    #region Methods
    public virtual bool ProcessMessage(IMessage message)
    {
        try
        {
            if (proxy != null)
                return proxy.ProcessMessage(message);
            else
                return false;
        }
        catch (Exception ex)
        {
            Console.WriteLine("Exception, ProcessMessage\r\n{0}", ex.Message);
            return false;
        }
    }
    public void Dispose()
    {
        this.Close();
    }
    #endregion
}
public class Proxy : ProxyBase<IMessageService>, IMessageService, IDisposable
{
    #region Constructors
    public Proxy(string endpoint) : base(endpoint) { }
    public Proxy(string endpoint, string address) : base(endpoint, address) { }
    #endregion

    #region Methods
    public virtual void ProcessMessage(IMessage message)
    {
        try
        {
            if (proxy != null)
                proxy.ProcessMessage(message);
        }
        catch (Exception ex)
        {
            Console.WriteLine("Exception, ProcessMessage\r\n{0}", ex.Message);
        }
    }
    public void Dispose()
    {
        this.Close();
    }
    #endregion
}

And now, how do I actually use these proxy classes? You’d have the usual WCF stuff defined in the .config in the endpoint in the systemServiceModel tags:

  <system.serviceModel>
    <client>
      <endpoint name="MyEndpoint"
          address="net.tcp://MyComputer:666/MyMessageService"
          binding="netTcpBinding" bindingConfiguration="netTcpConfig"
          contract="MyApp.IMessageService"/>
    </client>
  </system.serviceModel>

And then in your code, you’d call the WCF service like this:

using (Proxy proxy = new Proxy("MyEndpoint"))
{
    MyMessage message = new MyMessage();
    message.MessageToSend = "MyTestMessage";
    proxy.ProcessMessage(message);
}

Wow, that’s pretty easy, isn’t it? No need to add ServiceReferences or anything like that. All you need to know are the Interfaces that the Services use and you’re good to go!

9 comments:

  1. What? Seriousy, what? This would be sooo helpful to me if I understood it! Of all the languages I took in school and beyond (I didn't take any beyond, actually) I did not take Greek (Geek?) I am in awe, don't get me wrong! I have no idea but I know that those who do -- do! So, thank you, on their behalf!
    Sincerely, your not so geeky younger sister.

    ReplyDelete
  2. hahaha ... Shaz, you are the only one who's been commenting lately! Maybe no one is reading my blogs anymore ... =0(

    ReplyDelete
  3. Well Bon, I'm sure people are reading!! I know they are. They are gleaning all this great stuff from you and they are in such a hurry that they, more often than not, forget to post a comment! It's a blogger's reality!! Keep posting. Good stuff! Very good stuff! (I think!?) ;)

    ReplyDelete
  4. I got to your blog from Google - useful coding! I'm impressed (and you could have been a muse) have a nice day ... Gene Bird (MCSD ebird@ adjacent-tech.com Austin)

    ReplyDelete
  5. Hi Gene! Thanks for commenting ... I hope you can put some of my code to good use!

    ReplyDelete
  6. Hello Bonnie,

    This looks extremely useful. I can't wait to try it out.
    Thank you for sharing Bonnie!

    Nick

    ReplyDelete
  7. You're welcome, Nick! Let me know how you like it once you've tried it.

    ReplyDelete
  8. Looks excellent.
    Thanks a lot!

    C

    ReplyDelete
    Replies
    1. You're welcome, Cliff! I hope you find it useful!

      Delete