Praveen Katiyar' Blog

Learning along the way . . . .

Creating and Consuming a WCF Service without configuration files.

Introduction

This article explains how to create a simple WCF Service Host it on a specified port according to specified binding. all this you can do on the fly.

Background

Normally when we develop WCF Services, we are in habit of using Configuration files. (web.config/app.config depending on the environment). it is indeed very good idea to use configuration files. all this seems very automatic. And using configuration files is default way of doing things.

How this project is organized

In this article i have used 3 projects.

  • WCF Service (WCFMathLib.dll):  Actual Service logic, which defines a Service Contract Interface, OperationContract, and implements them, and exposes few functions to the world to use them.
  • Host Application (ConWCFMathHost.exe): Defines the logic to host the said service, according to the parameters supplied at the command line.
  • Client Application (ConWCFMathClient.exe): Client Application which will use this service.

First Part : WCF Service (WCFMathLib.dll):

The actual Service which implements the business logic. it defines an ServiceContract and few Operations available through Service. as shown below.  let;s call it IMathInterface.cs

to create this project you can simply take a Class Library Project, from while choosing from project wizard option. let’s Name it "WCFMathLib", it already contains a File Class1.cs, rename that file as MathService.cs, add one more file (IMathService.cs) to define the interface, although you can define interface this file also. below is the listing of both files.

 

Defining a Service Contract

// Listing of IMathInterface.cs
[ServiceContract]
public interface IMathService
{
    [OperationContract]
    double AddNumber(double dblX, double dblY);
   
    [OperationContract]
    double SubtractNumber(double dblX, double dblY);
   
    [OperationContract]
    double MultiplyNumber(double dblX, double dblY);
   
    [OperationContract]
    double DivideNumber(double dblX, double dblY);
}

Implementation  of Service Contract

//    Listing MathInterface.cs
public class MathService : IMathService
{
    public double AddNumber(double dblX, double dblY)
    {
        return (dblX + dblY);
    }

    public double SubtractNumber(double dblX, double dblY)
    {
        return (dblX – dblY);
    }

    public double MultiplyNumber(double dblX, double dblY)
    {
        return (dblX * dblY);
    }

    public double DivideNumber(double dblX, double dblY)
    {
        return ((dblY == 0 ) ? 0 : dblX / dblY);
    }
}

ServiceContract attribute

Service Contract. Describes which related operations can be tied together as a single functional unit that the client can perform on the service.

OperationContract attribute

An Operation contract specifyies that the said operation is exposed by the service, service defines the parameters and return type of an operation.

as one can see, there is nothing special here, just an Service Contract definition and its implementation.

Second Part : WCF Service (ConWCFMathHost.exe):

Host application has been developed as console based application, which host our Service (WCFMathLib.MathService), according to the supplied parameters, parameters are supplied in the following form.  it accepts two parameters.

ConWCFMathHost.exe TCP/HTTP portAdr <return>

Parameter 1 : Binding accepted values are HTTP and TCP

Parameter 2 : Port Number, and integer value specifyin the port value.

Hosting Service using HTTP Bidnding

private static void StartHTTPService( int nPort)
{
    string strAdr = "
http://localhost:" + nPort.ToString() + "/MathService";
    try
    {
        Uri adrbase = new Uri(strAdr);
        m_svcHost = new ServiceHost(typeof(MathService), adrbase);

        ServiceMetadataBehavior mBehave = new ServiceMetadataBehavior();
        m_svcHost.Description.Behaviors.Add(mBehave);
        m_svcHost.AddServiceEndpoint(typeof(IMetadataExchange), MetadataExchangeBindings.CreateMexHttpBinding(), "mex");

        BasicHttpBinding httpb = new BasicHttpBinding();
        m_svcHost.AddServiceEndpoint(typeof(IMathService), httpb, strAdr);
        m_svcHost.Open();
        Console.WriteLine("\n\nService is Running as >> " + strAdr );
    }
    catch (Exception eX)
    {
        m_svcHost = null;
        Console.WriteLine("Service can not be started as >> [" + strAdr + "] \n\nError Message [" + eX.Message + "]");
    }
}

Hosting Service using TCP Bidnding

private static void StartTCPService(int nPort)
{
    string strAdr = "net.tcp://localhost:" + nPort.ToString() + "/MathService";
    try
    {
        Uri adrbase = new Uri(strAdr);
        m_svcHost = new ServiceHost(typeof(MathService), adrbase);
        NetTcpBinding tcpb = new NetTcpBinding();

                ServiceMetadataBehavior mBehave = new ServiceMetadataBehavior();
        m_svcHost.Description.Behaviors.Add(mBehave);
        m_svcHost.AddServiceEndpoint(typeof(IMetadataExchange),   MetadataExchangeBindings.CreateMexTcpBinding(), "mex");

        m_svcHost.AddServiceEndpoint(typeof(IMathService), tcpb, strAdr);
        m_svcHost.Open();
        Console.WriteLine("\n\nService is Running as >> " + strAdr );
    }
    catch (Exception eX)
    {
        m_svcHost = null;
        Console.WriteLine("Service can not be started as >> [" + strAdr + "] \n\nError Message [" + eX.Message + "]");
    }
}

Code Description

Create Desired binding (TCP/HTTP)

ServiceMetadataBehavior, Controls the publication of service metadata and associated information. although we can omit the part shown in bold, as far as this project is concerned, as we are going to create everything by hand, but it is a good Idea, to always add a IMetadataExchange endpoint to the service. as it  exposes methods used to return the metadata about  the service. it is usefull when we are generating the proxy class and config files from the service using SVCUtil.exe utility.

Host Application Complete Code

// Program.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
using System.ServiceModel.Description;
using WCFMathLib;

namespace ConWCFMathHost
{
    class Program
    {
        static ServiceHost m_svcHost = null;
        static void Main(string[] args)
        {
            if (args.Count() != 2)
            {
                Console.WriteLine("Insufficient arguments supplied");
                Console.WriteLine("Service Host must be started as <Executable Name> <TCP/HTTP> <Port#>");
                Console.WriteLine("Argument 1 >> Specifying the Binding, which binding will be used to Host (Supported values are either HTTP or TCP) without quotes");
                Console.WriteLine("Argument 2 >> Port Number a numeric value, the port you want to use");
                Console.WriteLine("\nExamples");
                Console.WriteLine("<Executable Name> TCP 9001");
                Console.WriteLine("<Executable Name> TCP 8001");
                Console.WriteLine("<Executable Name> HTTP 8001");
                Console.WriteLine("<Executable Name> HTTP 9001");
                return;
            }

            string strBinding = args[0].ToUpper();
            bool bSuccess = ((strBinding == "TCP") || (strBinding == "HTTP"));
            if (bSuccess == false)
            {
                Console.WriteLine("\nBinding argument is invalid, should be either TCP or HTTP)");
                return;
            }
           
            int nPort = 0;
            bSuccess = int.TryParse(args[1], out nPort);
            if (bSuccess == false)
            {
                Console.WriteLine("\nPort number must be a numeric value");
                return;
            }

            bool bindingTCP = (strBinding == "TCP");
            if (bindingTCP) StartTCPService(nPort);  else StartHTTPService(nPort);
            if (m_svcHost != null)
            {
                Console.WriteLine("\nPress any key to close the Service");
                Console.ReadKey();
                StopService();
            }
        }

        private static void StartTCPService(int nPort)
        {
            string strAdr = "net.tcp://localhost:" + nPort.ToString() + "/MathService";
            try
            {
                Uri adrbase = new Uri(strAdr);
                m_svcHost = new ServiceHost(typeof(MathService), adrbase);
               
                ServiceMetadataBehavior mBehave = new ServiceMetadataBehavior();
                m_svcHost.Description.Behaviors.Add(mBehave);
                m_svcHost.AddServiceEndpoint(typeof(IMetadataExchange), MetadataExchangeBindings.CreateMexTcpBinding(), "mex");

                NetTcpBinding tcpb = new NetTcpBinding();
                m_svcHost.AddServiceEndpoint(typeof(IMathService), tcpb, strAdr);
                m_svcHost.Open();
                Console.WriteLine("\n\nService is Running as >> " + strAdr );
            }
            catch (Exception eX)
            {
                m_svcHost = null;
                Console.WriteLine("Service can not be started as >> [" + strAdr + "] \n\nError Message [" + eX.Message + "]");
            }
        }

        private static void StartHTTPService( int nPort)
        {
            string strAdr = "
http://localhost:" + nPort.ToString() + "/MathService";
            try
            {
                Uri adrbase = new Uri(strAdr);
                m_svcHost = new ServiceHost(typeof(MathService), adrbase);
               
                ServiceMetadataBehavior mBehave = new ServiceMetadataBehavior();
                m_svcHost.Description.Behaviors.Add(mBehave);
                m_svcHost.AddServiceEndpoint(typeof(IMetadataExchange), MetadataExchangeBindings.CreateMexHttpBinding(), "mex");

                BasicHttpBinding httpb = new BasicHttpBinding();
                m_svcHost.AddServiceEndpoint(typeof(IMathService), httpb, strAdr);
                m_svcHost.Open();
                Console.WriteLine("\n\nService is Running as >> " + strAdr );
            }
            catch (Exception eX)
            {
                m_svcHost = null;
                Console.WriteLine("Service can not be started as >> [" + strAdr + "] \n\nError Message [" + eX.Message + "]");
            }
        }

        private static void StopService()
        {
            if (m_svcHost != null)
            {
                m_svcHost.Close();
                m_svcHost = null;
            }
        }
    }
}

Third Part : Client (ConWCFMathClient.exe):

Client application has been also developed as console based application, parameters through command line in the following form.  you need to supply 6 parameters at the command line.

ConWCFMathClient <Host Machine> <TCP/HTTP> <Port#> <ADD/SUB/MUL/DIV> Num1 Num2

Parameter 1 : name/IP address of the machine, where service has been hosted.

Parameter 2 : Binding of the hosted service, accepted values are HTTP and TCP

Parameter 3 : Port Number, and integer value specifyin the port value.

Parameter 4 : Operation Code (accepted values are ADD, SUB, MUL, DIV)

Parameter 5 : Operand 1, operand value 1 for the operation.

Parameter 6 : Operand 2, operand value 2 for the operation.

Importing the Interface  definition

You can define the interface or just import the IMathService.cs file from the Service. this simply defines the interface, so that there is no error while compiling the client project

// Listing of IMathInterface.cs
namespace ConWCFMathClient
{
    [ServiceContract]
    public interface IMathService
    {
        [OperationContract]
        double AddNumber(double dblX, double dblY);
       
        [OperationContract]
        double SubtractNumber(double dblX, double dblY);
       
        [OperationContract]
        double MultiplyNumber(double dblX, double dblY);
       
        [OperationContract]
        double DivideNumber(double dblX, double dblY);
    }
}

  

Calling Methods of the Service

a function has been created to call  methods of the Service, passing all the parameter to the method, after validating them. here is the mehod.

private static void Evaluate(string strServer, string strBinding, int nPort, string strOper, double dblVal1, double dblVal2)
{
    ChannelFactory<IMathService> channelFactory = null;
    EndpointAddress ep = null;

    string strEPAdr = "http://" + strServer + ":" + nPort.ToString() + "/MathService";
    try
    {
        switch (strBinding)
        {
            case "TCP":
                NetTcpBinding tcpb = new NetTcpBinding();
                channelFactory = new ChannelFactory<IMathService>(tcpb);

                // End Point Address
                strEPAdr = "net.tcp://" + strServer + ":" + nPort.ToString() + "/MathService";
                break;

            case "HTTP":
                BasicHttpBinding httpb = new BasicHttpBinding();
                channelFactory = new ChannelFactory<IMathService>(httpb);
               
                // End Point Address
                strEPAdr = "
http://" + strServer + ":" + nPort.ToString() + "/MathService";
                break;
        }

        // Create End Point
        ep = new EndpointAddress(strEPAdr);

        // Create Channel
        IMathService mathSvcObj = channelFactory.CreateChannel(ep);
        double dblResult = 0;

            // Call Methods
        switch (strOper)
        {
            case "ADD": dblResult = mathSvcObj.AddNumber(dblVal1, dblVal2); break;
            case "SUB": dblResult = mathSvcObj.SubtractNumber(dblVal1, dblVal2); break;
            case "MUL": dblResult = mathSvcObj.MultiplyNumber(dblVal1, dblVal2); break;
            case "DIV": dblResult = mathSvcObj.DivideNumber(dblVal1, dblVal2); break;
        }

        //  Display Results.
        Console.WriteLine("Operation {0} ", strOper );
        Console.WriteLine("Operand 1 {0} ", dblVal1.ToString ( "F2"));
        Console.WriteLine("Operand 2 {0} ", dblVal2.ToString("F2"));
        Console.WriteLine("Result {0} ", dblResult.ToString("F2"));
        channelFactory.Close();
    }
    catch (Exception eX)
    {
        // Something unexpected happended ..
        Console.WriteLine("Error while performing operation [" + eX.Message + "] \n\n Inner Exception [" + eX.InnerException + "]");
    }
}

Code Description

Before you can call any method, you need to do two things.

Create a channel factory object, using the specified binding using .

for HTTP Bindng

BasicHttpBinding httpb = new BasicHttpBinding();
channelFactory = new ChannelFactory<IMathService>(httpb);

for TCP Bindng

NetTcpBinding tcpb = new NetTcpBinding();
channelFactory = new ChannelFactory<IMathService>(tcpb);
 

EndPoint address for HTTP binding

strEPAdr = "http://" + strServer + ":" + nPort.ToString() + "/MathService";

EndPoint address   TCP binding

strEPadr = strEPAdr = "net.tcp://" + strServer + ":" + nPort.ToString() + "/MathService";

Create a chaneel through specified end point aadress

// Create End Point ep = new EndpointAddress(strEPAdr); // Create ChannelIMathService mathSvcObj = channelFactory.CreateChannel(ep);                

Invoke methods through channel and display output.

double dblResult = 0;

// Call Methods
switch (strOper)
{
    case "ADD": dblResult = mathSvcObj.AddNumber(dblVal1, dblVal2); break;
    case "SUB": dblResult = mathSvcObj.SubtractNumber(dblVal1, dblVal2); break;
    case "MUL": dblResult = mathSvcObj.MultiplyNumber(dblVal1, dblVal2); break;
    case "DIV": dblResult = mathSvcObj.DivideNumber(dblVal1, dblVal2); break;
}

//  Display Results.
Console.WriteLine("Operation {0} ", strOper );
Console.WriteLine("Operand 1 {0} ", dblVal1.ToString ( "F2"));
Console.WriteLine("Operand 2 {0} ", dblVal2.ToString("F2"));
Console.WriteLine("Result {0} ", dblResult.ToString("F2"));

// Close channel
channelFactory.Close();
 

Point of Interest

  • No configuration file,
  • there is no proxy generation using SvcUtil

Client Application Complete Code

// Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
using System.ServiceModel.Description;

namespace ConWCFMathClient
{
    class Program
    {
        static void Main(string[] args)
        {
            if (args.Count() !=6 )
            {
                Console.WriteLine("Insufficient arguments supplied");
                Console.WriteLine("Service Host must be started as ConWCFMathClient <Host Machine> <TCP/HTTP> <Port#> <ADD/SUB/MUL/DIV> Num1 Num2");
                Console.WriteLine("Argument 1 >> IP address or the Machine name, where Service is hosted/running (localhost if running onm the same machine)");
                Console.WriteLine("Argument 2 >> Specifying the Binding type, which binding is used to Host the service. (Supported values are either HTTP or TCP),  without quotes");
                Console.WriteLine("Argument 3 >> Port Number a numeric value");
                Console.WriteLine("Argument 4 >> Operation permissible values are ADD/SUB/MUL/DIV without quotes");
                Console.WriteLine("Argument 5 >> Operand 1 for the operation ");
                Console.WriteLine("Argument 6 >> Operand 2 for the operation ");
                Console.WriteLine("\nExamples");
                Console.WriteLine("<Executable Name> 192.168.1.1 TCP 9001 ADD 1000 2000");
                Console.WriteLine("<Executable Name> 192.168.1.1 TCP 8001 SUB 500 1000");
                Console.WriteLine("<Executable Name> 192.168.1.1 HTTP 8001 MUL 500 1000");
                Console.WriteLine("<Executable Name> 192.168.1.1 HTTP 9001 DIV 3000 1000");
                Console.WriteLine("<Executable Name> localhost TCP 9001 ADD 4000.0  500.0");
                Console.WriteLine("<Executable Name> localhost TCP 8001 SUB 600.0 700.0");
                Console.WriteLine("<Executable Name> localhost HTTP 8001 MUL 1200.0 300.0");
                Console.WriteLine("<Executable Name> localhost HTTP 9001 DIV 2000.0 90.0");
                return;
            }

            string strAdr = args[0];
            string strBinding = args[1].ToUpper();
            bool bSuccess = ((strBinding == "TCP") || (strBinding == "HTTP"));
            if (bSuccess == false)
            {
                Console.WriteLine("\nBinding argument is invalid, should be either TCP or HTTP)");
                return;
            }

            int nPort = 0;
            bSuccess = int.TryParse(args[2], out nPort);
            if (bSuccess == false)
            {
                Console.WriteLine("\nPort number must be a numeric value");
                return;
            }

            string strOper = args[3].ToUpper();
            bSuccess = ((strOper == "ADD") || (strOper == "SUB") || (strOper == "MUL") || (strOper == "DIV"));
            if (bSuccess == false)
            {
                Console.WriteLine("\nOperation argument is invalid, should be ADD/SUB/MUL/DIV)");
                return;
            }

            //  Determine operand 1
            double dblNum1 = 0;
            bSuccess = double.TryParse(args[4], out dblNum1);
            if (bSuccess == false)
            {
                Console.WriteLine("\nOperand 1 must be a numeric value");
                return;
            }

            //  Determine operand 2
            double dblNum2 = 0;
            bSuccess = double.TryParse(args[5], out dblNum2);
            if (bSuccess == false)
            {
                Console.WriteLine("\nnOperand 2 must be a numeric value");
                return;
            }

            Evaluate(strAdr, strBinding, nPort, strOper, dblNum1, dblNum2);
        }

        private static void Evaluate(string strServer, string strBinding, int nPort, string strOper, double dblVal1, double dblVal2)
        {
            ChannelFactory<IMathService> channelFactory = null;
            EndpointAddress ep = null;

            string strEPAdr = "http://" + strServer + ":" + nPort.ToString() + "/MathService";
            try
            {
                switch (strBinding)
                {
                    case "TCP":
                    NetTcpBinding tcpb = new NetTcpBinding();
                    channelFactory = new ChannelFactory<IMathService>(tcpb);

                    // End Point Address
                    strEPAdr = "net.tcp://" + strServer + ":" + nPort.ToString() + "/MathService";
                    break;

                                        case "HTTP":
                    BasicHttpBinding httpb = new BasicHttpBinding();
                    channelFactory = new ChannelFactory<IMathService>(httpb);

                    // End Point Address
                    strEPAdr = "
http://" + strServer + ":" + nPort.ToString() + "/MathService";
                    break;
                }

                // Create End Point
                ep = new EndpointAddress(strEPAdr);

                // Create Channel
                IMathService mathSvcObj = channelFactory.CreateChannel(ep);
                double dblResult = 0;

                // Call Methods
                switch (strOper)
                {
                    case "ADD": dblResult = mathSvcObj.AddNumber(dblVal1, dblVal2); break;
                    case "SUB": dblResult = mathSvcObj.SubtractNumber(dblVal1, dblVal2); break;
                    case "MUL": dblResult = mathSvcObj.MultiplyNumber(dblVal1, dblVal2); break;
                    case "DIV": dblResult = mathSvcObj.DivideNumber(dblVal1, dblVal2); break;
                }
           
                //  Display Results.
                Console.WriteLine("Operation {0} ", strOper );
                Console.WriteLine("Operand 1 {0} ", dblVal1.ToString ( "F2"));
                Console.WriteLine("Operand 2 {0} ", dblVal2.ToString("F2"));
                Console.WriteLine("Result {0} ", dblResult.ToString("F2"));

                // Close channel
                channelFactory.Close();
            }
            catch (Exception eX)
            {
                // Something happended ..
                Console.WriteLine("Error while performing operation [" + eX.Message + "] \n\n Inner Exception [" + eX.InnerException + "]");
            }
        }
    }
}

//    Listing of IMathInterface.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;

namespace ConWCFMathClient
{
    [ServiceContract]
    public interface IMathService
    {
        [OperationContract]
        double AddNumber(double dblX, double dblY);
        [OperationContract]
        double SubtractNumber(double dblX, double dblY);
        [OperationContract]
        double MultiplyNumber(double dblX, double dblY);
        [OperationContract]
        double DivideNumber(double dblX, double dblY);
    }
}

Output

Running the Host Application as a TCP Service

image

Running the Client Application

image

Running the Host Application as a HTTP Service

Notewhile running the Service in HTTP Mode, you need to open command prompt in administrator mode.

image

Running the Client Application

image

Advertisements

September 12, 2013 Posted by | .NET, SOA, WCF | , , | Leave a comment