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
Running the Client Application
Running the Host Application as a HTTP Service
Note: while running the Service in HTTP Mode, you need to open command prompt in administrator mode.