Home » .Net, C#, Featured, Programming

A kick start tutorial for developing SOA/SAAS applications using WCF in .Net

24 November 2009 No Comment

Introduction

As other sample (Hello world) WCF services, this service is also going to help you understanding how to develop a simple WCF service and its client but I tried to explain the way more elaborately. My sample service is to provide employee details repository that stores as comma separated values in text files. You can always change the code as you want. Your approach can be a database in SQL Server, MySQL or XML files in a non-shared directories at your server. The main intention of this article is to teach people to develop a basic WCF service.

Understanding Contracts

Since the server and client are different and independent applications running either in the same machine or different machines in the network (intranet or internet), both the applications need to agree few things in order to communicate lucidly. These agreements are in different forms, viz., agreement to provide a service, agreement to provide different operations accessible over remotely and lively and any custom datatypes to be sent and received over the network.

DataContract and DataMember

I am having a class for the Employee entity named EmployeeType. This class contains three data members viz., ID of the Employee (integer), Name of the Employee (string) and the Salary drawn by him (float). The service and its client are going to communicate the information as an object of our class EmployeeType. If data that we are going to send and receive over the network is any of the primitive datatypes or .Net specific datatypes, then both of our .Net applications (service and its client) know it and absolutely they don’t need to sign-up any explicit agreements to use them. But, our object is of the type EmployeeType, which is of user defined and the service and its client need to know the class definition in-order to interpret its content.

[DataContract] attribute for the class definition is used to specify the underlying datatype is required to be serialized by the serializer during the run-time. Similarly, the class may have several datamembers in it and those which are accessible by both the service and its client need to be specified explicitly with an attribute [DataMember]. The class definition for our EmployeeType is as follows,

    [DataContract]
    public class EmployeeType
    {
        int iD = 0;
        string name = "";
        float salary = 0.0F;
        [DataMember]
        public int ID
        {
            get { return iD; }
            set { iD = value; }
        }

        [DataMember]
        public string Name
        {
            get { return name; }
            set { name = value; }
        }

        [DataMember]
        public float Salary
        {
            get { return salary; }
            set { salary = value; }
        }
    }

ServiceContract and OperationContract

We have successfully defined the communicable datatype. Now its time to define how this communication is going to be done. [ServiceContract] attribute helps us to specify service classes or the interfaces to be implemented by service classes. Similarly, the [OperationContract] attribute is used to specify the methods to be exposed in remote applications. The definitions of these operations shall be available and executed during runtime only at server side but the remote clients of the service shall have the know the signature of these operations (parameters and return types) in-order to invoke them remotely in real-time.

In our employee details service, we are planning to store the employee object received from the remote client and retrieve the employee object, when the employee-id is passed, and sending back to the remote client requesting it. Before implementing the class to do these two operations, let us define an interface for it.

    [ServiceContract]
    public interface IEmployeeDetails
    {
        [OperationContract]
        bool SetDetails(EmployeeType employee);

        [OperationContract]
        EmployeeType GetDetails(int EmployeeID);
    }

Kindly note: As we have discussed already, the SetDetails and GetDetails methods are specified with [OperationContact] attributes and the interface is specified with the attribute [ServiceContract]. This ensures the class implements this interface exposes two methods to be invoked from the remote client.

Requirements and System Design

Employee details (Employee ID, Name, and Salary) have to be stored at server in a pain text file (independent file for each employee) with the name of employee id and the extension “.emp”. Directory in which all the employee files need to be stored shall be configured through the application configuration file. All the fields (employee id, name and salary) shall be stored as a comma separated plain text in the file.

When the remote client invokes the service method SetDetails(), it shall pass the EmployeeType Object with values assigned to its members and the method shall return boolean value indicating whether the store operation success or not.

When the GetDetails method of the service is invoked by the remote client by passing the employee id, the service shall look for the file with the employee id in its name. If the one exists, it shall read and interpret the comma seperated employee details, put them in an object of the EmployeeType class and return it to the client; otherwise a null value shall be returned indicating there is no such employee detail found.

Implementation of the Service class

Following is the code for EmployeeDetailsService implementing our IEmployeeDetails interface,

    public class EmployeeDetailsService : IEmployeeDetails
    {
        #region IEmployeeDetails Members
        public bool SetDetails(EmployeeType employee)
        {
            // To store the result (success or fail)
            bool resultOfCreation = false;

            // Create a reader used to read the application configuration
            AppSettingsReader asr = new AppSettingsReader();

            // Writer used to create/update the employee details text file
            StreamWriter sWri = null;
            try
            {
                // Read the Directory path under the key FilePath from the configuration file
                string filename = asr.GetValue("FilePath", Type.GetType("System.String")).ToString();

                // Combine the path with the file name (employee-id.emp)
                filename = Path.Combine(filename, employee.ID.ToString().Trim().ToUpper() + ".emp");

                // Create a text file with the newly formed file name; if the one already exists replace it
                sWri = File.CreateText(filename);

                // Write the employee details passed (Id,Name and Salary) as a comma seperated string
                sWri.Write(employee.ID.ToString() + "," + employee.Name + "," + employee.Salary.ToString());

                // Flush the output buffer
                sWri.Flush();

                // Close the file
                sWri.Close();

                // Mark the result of creation as success
                resultOfCreation = true;

            }
            catch (Exception)
            {
                // If any exception caught, mark the result of creation as failed
                resultOfCreation = false;
            }
            finally
            {
                // If the writer object is still referred, dispose it
                if (sWri!=null) sWri.Dispose();
                asr = null;
            }

            // Return the marked result
            return resultOfCreation;
        }

        public EmployeeType GetDetails(int EmployeeID)
        {
            // Employee Details object to be extracted from the storage
            EmployeeType et = null;

            // Reader to read the employee details text file
            StreamReader sr = null;

            // Create a reader for reading application configuration
            AppSettingsReader asr = new AppSettingsReader();
            try
            {
                // Read the employee file storage path from the application configuration
                string filename = asr.GetValue("FilePath", Type.GetType("System.String")).ToString() ;

                // Form the file name for the given employee id
                // and directory path read from configuration file
                // and open the text file for reading
                // If any error occurs let us assume that there is no
                // such an employee details file found
                sr = File.OpenText(Path.Combine(filename, EmployeeID.ToString().Trim().ToUpper() + ".emp"));

                // read the first line
                string dataGot = sr.ReadLine();

                // split the strings with comma
                string[] details = dataGot.Split(',');

                // create a new EmployeeType object
                et = new EmployeeType();

                // extract the details into the new employee object
                et.ID = Convert.ToInt32(details[0]);
                et.Name = details[1];
                et.Salary = Convert.ToSingle(details[2]);
            }
            catch (Exception)
            {
                // Do the needful, presently not coded
            }
            finally
            {
                // If the file reader object is still referred dispose it
                if (sr != null)
                {
                    sr.Close();
                    sr.Dispose();
                }
                sr = null;
                asr = null;
            }

            // Return the employee details object to the caller
            return et;
        }
        #endregion
    }

Read the comments embedded in the code for understanding the implementation clearly.

Putting it in a Visual Studio.Net project

Open Visual Studio.Net 2008 create a new project of the type Visual C# WCF Service Library with the name EmployeeDetails. When it opens, in the Solution Explorer, rename the IService1.cs file to IEmployeeDetails.cs and double click it to open. Carefully replace the Interface code starts after [ServiceContract] attribute with the interface code given in this article. Similarly, replace the content class CompositeType starts after [DataContract] with the code for the class EmployeeType given in this article.

Next, rename the file Service1.cs to EmployeeDetailsService.cs in the solution explorer and double click to open it in the editor. Carefully replace the class EmployeeDetailsService code with the code given for the same in this article. Don’t forget to include the using System.IO namespace since our service accesses the .Net IO library for creating and reading directory contents.

Kindly Note: The System.ServiceModel namespace is already included in the project as we have created an WCF Service Library project

One more change that we should do is changing the substring in the base address path from Service1 to EmployeeDetailsService (found in the following hierarchy of tags system.ServiceModel > services > service > host > baseAddresses) in the App.Config File of the service project just we have created. For example the path initially found was http://localhost:8731/Design_Time_Addresses/EmployeeDetails/Service1/; I have changed this to http://localhost:8731/Design_Time_Addresses/EmployeeDetails/EmployeeDetailsService/.

Hosting the service in a running application

Now the employee details service is ready for hosting. You can host the WCF service in any type of project in visual studio such as Windows Service, Windows Desktop Application. I have chosen a Windows Forms Application for hosting the employee details service. To do so, right click the solution from the Solution explorer, select Add > New Project. In the New project dialog box select Visual C# > Windows > Windows Forms Application and name the project as HostForEmployeeDetailsService. Rename the Form1.cs to frmHost.cs from the Solution Explorer.

Right click the References in the HostForEmployeeDetailsService project (henceforth to be referred as HostApplication) and choose Add Reference. In the .Net tab find and add the component with the name System.ServiceModel (Remember, for WCF Service Library project this was added automatically but here we have to do it manually). Again right click on the project references and select the Add References Option; but this time go to Projects tab select the project with the name EmployeeService (it is the only one available project) and click OK to add it to our host application.

Double click the form to open the designer of it in the editor.

Service Host

Service Host

Place Two buttons with the names btnStartService and btnStopService Respectively. Change their texts also to Start and Stop respectively. Initially make the Stop button disabled (Set Enabled property of the stop button to false in its properies window under behavior group). Place a label to show the status of the service with the name lblStatus wherever you want in the form.

In the form class (found in code view), add the line following line to declare the service host object.

ServiceHost sh = null;

Kindly Note: Don’t forget to include the using System.ServiceModel; and using EmployeDetails; statements in the form code. Double click the Start button and in the resulting code view carefully replace the following code,

        private void btnStartService_Click(object sender, EventArgs e)
        {
            // Employee Details Service Address
            Uri nettcpUri = new Uri("net.tcp://localhost:8000/");

            // Service Base Address
            Uri httpBaseUri = new Uri("http://localhost:9000/");

            // Create a new host for the service with the URIs just created
            sh = new ServiceHost(typeof(EmployeeDetails.EmployeeDetailsService), new Uri[] { nettcpUri, httpBaseUri });

            // Create a service meta data behavior
            ServiceMetadataBehavior smdb = new ServiceMetadataBehavior();

            // Enable accessing the meta data via http
            smdb.HttpGetEnabled = true;

            // Add the newly created behavior to the service host
            sh.Description.Behaviors.Add(smdb);

            // Add the endpoint to access the service with the .Net's TCP Binding
            sh.AddServiceEndpoint(typeof(EmployeeDetails.IEmployeeDetails), new NetTcpBinding(), "EmployeeDetails");

            // Add the endpoint to access and get the meta data
            // by the clients using meta data exchange bindings
            sh.AddServiceEndpoint(typeof(EmployeeDetails.IEmployeeDetails), MetadataExchangeBindings.CreateMexTcpBinding(), "MEX");

            // Open the service for access
            sh.Open();

            // Show the status of the Service is Running
            lblStatus.Text = "Service is Running";

            // Disable the Start button and enable the Stop button
            btnStartService.Enabled = false;
            btnStopService.Enabled = true;
        }

The service is going to have TCP binding at port 8000 and this is specified in the first endpoint. the metadata exchange and other discoveries/transactions are going to happen in the next address which is at the port 9000.

Endpoints are nothing more than connecting points (included with specifications such as address, protocol, etc.) exposed outside the service which are used to access the service by its clients. Kindly go through the detailed comments for understanding the code.

Similarly, double click on the Stop button and replace the code for Click handler code of the stop button with the following code,

        private void btnStopService_Click(object sender, EventArgs e)
        {
            // If the service host is still referred, close it
            if (sh != null) sh.Close();
            sh = null;

            // update the status in the label as Service is Stopped
            lblStatus.Text = "Service is Stopped";

            // Disable the Stop button and enable the Start button
            btnStartService.Enabled = true;
            btnStopService.Enabled = false;
        }

Remember, we have to include the configuration setting for specifying the directory in the server to store the employee detail files in the application configuration file of the host application. Right click the project in the Solution Explorer select Add > New Item and in the resulting dialog box, select Application Configuration File and add it. Open the configuration file by double Inside the Configuration tags, add appSettings tag and inside the appSettings tags, add new setting with key as “FilePath” and value as folder name to store the employee detail files (for example  “D:\EmployeeFiles”). Make sure that the folder path exists at the server. Finally, right click the host application and set as startup project.

Kindly Note: When you develop/debug/run this application in Vista or higher operating systems, please read the article below about how to make your application take privileges for creating a binding with a port.
Elevate privileges of your .Net applications in Windows Vista

Now our WCF server is ready to provide the employee details repository service.

WCF Client

Employee Details Terminal

Employee Details Terminal

Open another instance of Visual Studio.Net and create new Windows Forms Application with the name ClientForEmployeeDetailsService in it. Change the default form name from Form1.cs to frmClient.cs. Double click the form in Solution Explorer to open its designer. Place three labels one below the other and three textboxes beside each of the labels. Also place two buttons. Names of the labels shall be respectively, lblEmployeeID, lblEmployeeName and lblSalary and texts of them shall be “Employee ID:”, “Employee Name:”, “Salary:”.

Change the text box names respectively txtEmployeeID, txtEmployeeName and txtSalary. Rename the two buttons with names btnSave and btnGet respectively. Change text property of these buttons to “Save at Server” and “Get from Server” respectively.

Now, the service reference has to be added to the client project which will extract the endpoints and interfaces exposed by the service via network. Before adding the service reference make sure that the service that we have previously coded is running (Run the host application and press the start button). To add the service reference to the client project, right click the client project and select Add Service Reference. In the appearing dialog box, type the base address of the service (http://localhost:9000) and press the Go button. When the service is found, it will show the EmployeeDetailsService and the Service contract (IEmployeeDetails) under it in the Services area. When you select the IEmployeeDetails the operations will be shown in the rightside Operations area. Select the EmployeeDetailsService and press the OK button.

Add Service Reference

Add Service Reference

When we add the service reference to the client project, the required modifications in its application configuration file and the reference to the System.ServiceModel namespace will be automatically done in it. Now add the following code to the form class (above the constructor of the form class),

EmployeeDetailsServiceReference.EmployeeDetailsClient proxy = null;

Double click on the form and replace the Load event of the form with the following code,

        private void frmClient_Load(object sender, EventArgs e)
        {
            proxy = new EmployeeDetailsServiceReference.EmployeeDetailsClient("NetTcpBinding_IEmployeeDetails", new System.ServiceModel.EndpointAddress("net.tcp://localhost:8000/EmployeeDetails"));
        }

Double click the “Save at Server” button and replace event with the following code,

        private void btnSave_Click(object sender, EventArgs e)
        {
            // Employee ID
            int empid;

            // Salary
            float sal;

            // Interpret the employee id entered in the txtEmployeeID into empid
            // If it is not an integer, show the error message and quit
            if (!Int32.TryParse(txtEmployeeID.Text.Trim(), out empid))
            {
                MessageBox.Show("Employee ID should be an integer");
                return;
            }
            // Interpret the salary entered in the txtSalary into sal
            // If it is not a floating point number show the error message and quit
            if (!Single.TryParse(txtSalary.Text.Trim(), out sal))
            {
                MessageBox.Show("Salary should be a valid floating point number");
                return;
            }

            // Create object of the type EmployeeType available in the Service reference
            EmployeeDetailsServiceReference.EmployeeType n = new ClientForEmployeeDetailsService.EmployeeDetailsServiceReference.EmployeeType();

            // Populate the details given in the textboxes into the object
            n.ID = empid;
            n.Name = txtEmployeeName.Text.Trim();
            n.Salary = sal;

            // Call the remote method SetDetails() of the proxy object
            // If the returned value is true show the success message;
            // otherwise failure
            if (proxy.SetDetails(n))
                MessageBox.Show("Saved at Server");
            else
                MessageBox.Show("Unable to store the employee details at Server");
        }

Double click the “Get from Server” button and replace the event code with the following,

        private void btnGet_Click(object sender, EventArgs e)
        {
            // Employee ID
            int empid;

            // Interpret the employee id as integer, if not show error and exit
            if (Int32.TryParse(txtEmployeeID.Text.Trim(), out empid))
            {
                // Upon the successfull interpretation, call the remote method GetDetails()
                // of the proxy. The return value will be the Object EmployeeType contains
                // the details of the employeeid passed if found and, if the employee id
                // not found at the server a null value
                EmployeeDetailsServiceReference.EmployeeType et = proxy.GetDetails(empid);

                // If the employee object returned, extract the information
                // and display it in the textboxes
                if (et != null)
                {
                    txtEmployeeName.Text = et.Name;
                    txtSalary.Text = et.Salary.ToString();
                }
                else // Otherwise show the error and exit
                {
                    MessageBox.Show("Unable to find the employee with the given ID at server");
                }
            }
            else
                MessageBox.Show("Enter an integer value in Employee ID to search!");
        }

Kindly Note: You are kindly requested to read the comments for understanding what each of the statement is doing.
Now the client is also ready for accessing the service.

Line Break

Author: Ganesh Kumar (15 Articles)

Ganesh Kumar

Ganesh Kumar has qualified with his Masters in Technology with Distinction. His total experience is about 6 years in Development and 2 years in Teaching. Presently he is working for WDC Ltd., Kolkata, India in C#, .Net and SQL Server.

Leave your response!

Add your comment below, or trackback from your own site. You can also subscribe to these comments via RSS.

Be nice. Keep it clean. Stay on topic. No spam.

You can use these tags:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

This is a Gravatar-enabled weblog. To get your own globally-recognized-avatar, please register at Gravatar.

Spam protection by WP Captcha-Free

Get Adobe Flash playerPlugin by wpburn.com wordpress themes