How to write your own Windows Communication Foundation proxy

Note: This article assumes you already know how to write the WCF service. I am just giving you an example of how to talk to it without using the goofy auto-generated class.

I see a lot of developers who write WCF services like to use the auto-generated proxy classes for communicating with there service. The problem is: What if you want to add custom code to the proxies? Your changes are going to get wiped when you refresh the proxy class.

If you don’t plan on ever doing anything more complicated with your service, or you simply want to take the easy route, by all means use the auto-generated proxy from Visual Studio. This will work for most people. If you want more control, and to be able to update the proxy on your own terms, then you can use the following code and tips to write your own hand rolled proxy code.

It is still very simple to write and use, plus it looks so much cleaner.

You will need to first create your contracts, which you need no matter how you wish to write your service. The contracts for Window Communication Foundation services are your key to allowing the client application to speak to your service. It is best practice to write explicit contracts so you can separate your business entities from your data transfer objects (DTO’s), but you already knew that.

Let’s pretend that our Service Contract, the contract that represents the methods available for invocation by a client, is defined as IMyService for the sake of our example.

Since you may have multiple services, always plan for the future, you will want to write a base proxy that you can inherit from in your proxy class.

Let’s define the base proxy to be nice and simple. In the future, or a future article, you can expand the proxy to allow for adding to the message headers or utilize more advanced WCF features. Base Proxy Definition:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Security.Cryptography.X509Certificates;
using System.Configuration;
using System.ServiceModel;
using System.Windows.Forms;

namespace PwC.Audition.Server.Proxies.ClientServices
{

    /// <summary>
    /// Use for handling some core functionality like cert handling and security
    /// </summary>
    public abstract class BaseProxyClient<TChannel> : System.ServiceModel.ClientBase<TChannel> where TChannel : class
    {
        string userId;

        // I like to tell my service what version I am, I usually throw this into the headers. But that’s for a later article.
        protected static string ClientVersion
        {
            get
            {
                return ConfigurationSettings.AppSettings["ClientVersion"];
            }
        }

        /// <summary>
        /// Creates the secured context scope. Put IMMEDIATELY into a USING statement. Must be disposed.
        /// </summary>
        /// <param name="channel">The channel.</param>
        /// <returns>Active operation context scope prefilled with security descriptors</returns>
        protected OperationContextScope CreateSecuredContextScope()
        {
            OperationContextScope scope = new OperationContextScope((IClientChannel)Channel);

            //Out of scope of this post, but I put all of my header entries here by writing to the operation scope.
            // you then add the userID and other items to the header
            return scope;

        }

        /// <summary>
        /// Inits the security information.
        /// </summary>
        private void InitSecurityInformation()
        {
            //  userId = GetCurrentUser(); simple example of storing the user id from whatever location you wish. This is then used to put into header
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="BaseProxyClient"/> class.
        /// </summary>
        public BaseProxyClient()
        {
            InitSecurityInformation();
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="BaseProxyClient<TChannel>"/> class.
        /// </summary>
        /// <param name="endpointConfigurationName">Name of the endpoint configuration.</param>
        public BaseProxyClient(string endpointConfigurationName) :
            base(endpointConfigurationName)
        {
            InitSecurityInformation();
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="BaseProxyClient<TChannel>"/> class.
        /// </summary>
        /// <param name="endpointConfigurationName">Name of the endpoint configuration.</param>
        /// <param name="remoteAddress">The remote address.</param>
        public BaseProxyClient(string endpointConfigurationName, string remoteAddress) :
            base(endpointConfigurationName, remoteAddress)
        {
            InitSecurityInformation();
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="BaseProxyClient<TChannel>"/> class.
        /// </summary>
        /// <param name="endpointConfigurationName">Name of the endpoint configuration.</param>
        /// <param name="remoteAddress">The remote address.</param>
        public BaseProxyClient(string endpointConfigurationName, System.ServiceModel.EndpointAddress remoteAddress) :
            base(endpointConfigurationName, remoteAddress)
        {
            InitSecurityInformation();
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="BaseProxyClient<TChannel>"/> class.
        /// </summary>
        /// <param name="binding">The binding.</param>
        /// <param name="remoteAddress">The remote address.</param>
        public BaseProxyClient(System.ServiceModel.Channels.Binding binding, System.ServiceModel.EndpointAddress remoteAddress) :
            base(binding, remoteAddress)
        {
            InitSecurityInformation();
        }

    }
}

Using the base proxy, we can now create our class to handle the interface of our service. Note, I included a simple template in the base proxy for adding header information. You can easily omit the InitSecurityInformation call as well as the CreateSecuredContextScope if you don’t ever plan to work with message headers. If there is enough demand, I can cover the usage of the message headers in WCF and how they can be very useful for passing information to the server more easily. The service below shows an example of a contract that has two methods. IsAlive and TotalUptime. You would implement the actual methods and interface for your particular needs. If you notice how easy it is to implement, a simple call to base.Channel.MethodName lets you call your service.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Seekford.App.Server.Contracts.MyService //<---YOUR SERVICE CONTRACT HERE
using System.Collections.ObjectModel;

namespace Seekford.App.Server.Proxies
{
    /// <summary>
    /// Proxy to utilize the My Service.
    /// </summary>
    public class MyServiceClient : BaseProxyClient<IMyService> , IMyService //<-- Implement the interface for the contract. If you don't want to implement every method, you can then remove this and not full implement all of them. Useful for exposing only a subset of your service.
    {

         #region constructors
        /// <summary>
        /// Initializes a new instance of the <see cref=" MyServiceClient "/> class.
        /// </summary>
        /// <param name="endpointConfigurationName">Name of the endpoint configuration.</param>
        public MyServiceClient(string endpointConfigurationName) :
            base(endpointConfigurationName)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref=" MyServiceClient "/> class.
        /// </summary>
        /// <param name="endpointConfigurationName">Name of the endpoint configuration.</param>
        /// <param name="remoteAddress">The remote address.</param>
        public MyServiceClient (string endpointConfigurationName, string remoteAddress) :
            base(endpointConfigurationName, remoteAddress)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref=" MyServiceClient "/> class.
        /// </summary>
        /// <param name="endpointConfigurationName">Name of the endpoint configuration.</param>
        /// <param name="remoteAddress">The remote address.</param>
        public MyServiceClient (string endpointConfigurationName, System.ServiceModel.EndpointAddress remoteAddress) :
            base(endpointConfigurationName, remoteAddress)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref=" MyServiceClient "/> class.
        /// </summary>
        /// <param name="binding">The binding.</param>
        /// <param name="remoteAddress">The remote address.</param>
        public MyServiceClient (System.ServiceModel.Channels.Binding binding, System.ServiceModel.EndpointAddress remoteAddress) :
            base(binding, remoteAddress)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="MyServiceClient"/> class.
        /// </summary>
        public MyServiceClient ()
            : base("TCP_IMyService")
        {

        }

        #endregion

        /// <summary>
        /// Determines whether this instance is alive.
        /// </summary>
        /// <returns>
        /// 	<c>true</c> if this instance is alive; otherwise, <c>false</c>.
        /// </returns>
        public bool IsAlive()
        {
            using (var scope = CreateSecuredContextScope())
            {
                return base.Channel.IsAlive();
            }
        }

        /// <summary>
        /// Totals the uptime.
        /// </summary>
        /// <returns></returns>
        public TimeSpan TotalUptime()
        {
            using (var scope = CreateSecuredContextScope())
            {
                return base.Channel.TotalUptime();
            }
        }

    }
}

Of course, you need to make sure you either setup your app.config with the connection information or you supply the server information to the constructor at run time. If adding to the configuration file, the following is a simple example of what you would need to do. I bolded the items that are pertinent. You will need to match your configuration to your needs. If you are using http for example.

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <system.serviceModel>
    <bindings>
      <netTcpBinding>
        <binding name="TCP_Binding" closeTimeout="00:30:00" openTimeout="00:01:00" receiveTimeout="00:30:00" sendTimeout="00:30:00" maxBufferSize="500000000" maxReceivedMessageSize="500000000">
          <readerQuotas maxDepth="32" maxStringContentLength="2147483647" maxArrayLength="2147483647" maxBytesPerRead="2000000" maxNameTableCharCount="16384"/>
        </binding>
      </netTcpBinding>
    </bindings>
    <client>
     <endpoint address="net.tcp://MyServer:9090/seekford/1.0/MyService"   binding="netTcpBinding"
               bindingConfiguration="TCP_Binding"
               contract="Seekford.App.Server.Contracts.MyService"
               name="TCP_MyService">
        <identity>
          <dns value="localhost" />
        </identity>
      </endpoint>
   </client>
  </system.serviceModel>
</configuration>

Now that you see how simple it is to write your own clean proxy class, you will wonder why you were always using the auto generated proxies.

It is so simple and clean.

Happy coding!

Leave a Reply

Your email address will not be published. Required fields are marked *