Hello Friends, this article is basically an updated version of my previous article, if you haven't look at it kindly go through the following link:
In the article above there is an issue that the client is unable to determine what has happened to his request, in other words there is no response from the server to the client about his request status. Now in this article we will try to implement a two way communication between the server and the client, in other words whenever the client sends a request to the server, let the server be available or if not then the client request will be maintained in a MSMQ queue and whenever the server becomes available the client request is processed and a status of the request is sent as a response to the client for it. In this case also it may be possible that the client may not be always be available. If the client is not available then the response from the server will be maintained in some other queue and whenever the client becomes available he or she will be able to see the response from the server. This scenario can be implemented by making a transactional queue. In the previous article we created a non-transactional queue. There are various benefits if we create a transactional queue, they are:
- Message request or response will be delivered once.
- Messages are stored in the queue until the client or server becomes available.
- Making use of transactions ensure that the failed messages are totally rolled back to that particular client.
- It supports all transaction features such as automatic commit or rollback if any message fails.
- We can use Distributed Transaction Management.
- For implementing the MSMQ Transactional mode we must set the following things:
- For making MSMQ queue transactional we must set the second parameter of the MessageQueue.Create method to true in the code file.
- In the app.config file we must set the exactlyOnce attribute to true for transactional queues.
- Also in operation contract behavior we must set the following attribute [OperationBehavior(TransactionAutoComplete = true, TransactionScopeRequired = true)] that specifies that the transaction must execute inside the same transaction scope and once finished executing should commit automatically.
Now moving towards our topic, what we are going to implement is that we are having a table in my Oracle Database with the name Employee that has certain fields such as (Empid, Name, Address, City and Salary). Now there will be various clients who will try to update their respective records. The client will send their update to the server and upon the successful update of the record the client should get a response from the server that their record has been updated. Now in either of the cases i.e. (client sending an update request to the server or the server sending a response to the client) it may happen that either of the partys may not be available. So when that happens we can use a MSMQ transactional queue to maintain the request and responses of various clients to the server and vice versa.
Now to maintain the request of the server and responses from the client we need to maintain two separate queues, one for the request (used by the server to check for requests from clients) and another for responses (used by the client to get the status of their response). There will be two hosts in our application one will be taking care of the server side MSMQ queue requests of the client's and the other will be at the client side that will be responsible for displaying or fetching the responses of the server stored on the client's MSMQ queue. In other words, whatever the client request is, it will be stored on the server's MSMQ queue and whatever the server's response is, it will be stored on the client's MSMQ queue. With this concept, if either of the partys are not available then the request or response will be stored in the respective server or the client's queue and later on when either of the unavailable partys become available it will do further processing of the request or response accordingly.
Points to keep in mind
- There will be two MSMQ queues that will be used by the server and client respectively for fetching the request (in the case of a server) and for displaying the message (in the case of clients).
- There will be two service hosts, one will be at the server side (the actual server that will update the data in the database) and the other will be at the client side that will display the status of the response of what had happend to his request.
- Both the server and the client service host will be tied up. i.e both will be referencing each other so that they can transfer messages to each other and the messages can be displayed on their respective screens.
- In this case referencing means both will have the WSDL of each other's service. As I said, there will be two services, one will be the actual server that will take care of updating the record in the database and the other will be the client server where the status of the update will be displayed.
- All the operation contracts under both services will be executed in the transactional mode to maintain reliability and consistency.
- Now in the following I'm providing the name of the servers and their service contract, operation contract and their host server names.
- For Server Side We have used EmpService that implements IEmployee Service contract that contains UpdateEmployeeDetails as the operation contract.
- For ClientFlashStatus We have used FlashStatusService that implements an IFlashStatus Service Contract that contains UpdateStatus as the operation contract.
- EmpUpdateServer (the actual Server) is the server that will be hosting our EmpService.
- FlashStatusHost (the client Server) will be the server that will be hosting our FlashStatusService.
- So the hierarchy of our application is something like the following:
Now if you are familiar with what we are going to do we can move further with our code explaination. I'll try to maintain my explaination as simple as I can. We'll try to complete the demo through a number of steps.
I. Oracle Table, Procedures
- CREATE TABLE HQM_ADMIN.EMPLOYEE
- (
- EMPID VARCHAR2(20 BYTE),
- NAME VARCHAR2(30 BYTE),
- ADDRESS VARCHAR2(30 BYTE),
- CITY VARCHAR2(30 BYTE),
- SALARY NUMBER
- )
Procedures
-
- CREATE OR REPLACE procedure HQM_ADMIN.prc_CheckEmployeeExists
- (
- v_eid varchar,
- v_exists out varchar
- )
- as
- v_count number:=0;
- begin
- select count(*) into v_count from Employee where Empid=v_eid;
- if(v_count>0)
- then
- v_exists:='true';
- else
- v_exists:='false';
- end if;
- end;
-
- //Procedure for updating a record in the table.
- CREATE OR REPLACE procedure HQM_ADMIN.prc_UpdateEmpDetails
- (
- eid varchar,
- ename varchar,
- eadd varchar,
- ecity varchar,
- esal number
- )
- as
- begin
- update employee
- set Name=ename,Address=eadd,City=ecity,Salary=esal
- where empid=eid;
- end;
II. WCF Service
1. First of all for creating any WCF app as we all know we require a service contract and a service. The following is the code for that. I've created a service contract with the name IEmployee (which basically has one "OperationContract(IsOneWay=true)" named UpdateEmployeeDetails) and a service with the name EmpService that implements an IEmployee service contract.
IEmployee service contract
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- using System.ServiceModel;
- namespace EmployeeUpdateReportComponent
- {
- [ServiceContract]
- public interface IEmployee
- {
- [OperationContract(IsOneWay = true)]
- void UpdateEmployeeDetails(string eid, string ename, string eadd, string ecity, double salary, string reportUpdateStatusTo);
- }
- }
EmpService.cs
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- using System.Data;
- using System.Transactions;
- using System.ServiceModel;
- using System.Data.OracleClient;
-
- using EmployeeUpdateReportComponent.FlashStatusServiceRef;
- namespace EmployeeUpdateReportComponent
- {
- public class EmpService : IEmployee
- {
- private string _dbCon = "DATA SOURCE=XXX;WORD=XXX;PERSIST SECURITY INFO=True;USER ID=XXX";
- OracleConnection con;
- OracleCommand cmd;
-
-
- [OperationBehavior(TransactionAutoComplete = true, TransactionScopeRequired = true)]
- public void UpdateEmployeeDetails(string eid, string ename, string eadd, string ecity, double esalary, string reportUpdateStatusTo)
- {
- Console.WriteLine("Processing Employee: " + eid + " details");
- con = new OracleConnection(_dbCon);
- if (CheckEmployeeExists(eid, con))
- {
- cmd = new OracleCommand("prc_UpdateEmpDetails", con);
- cmd.CommandType = CommandType.StoredProcedure;
- cmd.Parameters.AddWithValue("eid", eid);
- cmd.Parameters.AddWithValue("ename", ename);
- cmd.Parameters.AddWithValue("eadd", eadd);
- cmd.Parameters.AddWithValue("ecity", ecity);
- cmd.Parameters.AddWithValue("esal", esalary);
- con.Open();
- int rows = cmd.ExecuteNonQuery();
- con.Close();
-
- FlashStatusClient flashClient = new FlashStatusClient(new NetMsmqBinding(), new EndpointAddress(reportUpdateStatusTo));
-
- using (TransactionScope scope = new TransactionScope(TransactionScopeOption.Required))
- {
- string updateStatus = rows > 0 ? "Updated" : "Pending";
-
- flashClient.UpdateStatus(eid, ename, eadd, ecity, esalary, updateStatus);
-
- scope.Complete();
- Console.WriteLine("Done Processing Of Employee: " + eid);
- }
- }
- }
-
-
-
-
-
-
- private bool CheckEmployeeExists(string eid, OracleConnection con)
- {
- if (con.ConnectionString == "")
- con = new OracleConnection(_dbCon);
- cmd = new OracleCommand("prc_CheckEmployeeExists", con);
- cmd.CommandType = CommandType.StoredProcedure;
- cmd.Parameters.AddWithValue("v_eid", eid);
- OracleParameter para = new OracleParameter();
- para.ParameterName = "v_exists";
- para.Size = 10;
- para.OracleType = OracleType.VarChar;
- para.Direction = ParameterDirection.Output;
- cmd.Parameters.Add(para);
- con.Open();
- OracleDataReader dr = cmd.ExecuteReader();
- string returnValue = para.Value.ToString();
- con.Close();
- if (returnValue != "" && returnValue == "true")
- return true;
- else
- return false;
- }
- }
- }
3. Now we will try to create a server (Self Hosted) for our EmpService, in our case "EmployeeServerUpdateHost" will be our host.
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- using System.Messaging;
- using System.Transactions;
- using System.Configuration;
- using System.ServiceModel;
- using EmployeeUpdateReportComponent;
-
- namespace EmployeeServerUpdateHost
- {
- class EmpUpdateServer
- {
- static void Main(string[] args)
- {
-
- string queueName = ConfigurationManager.AppSettings["queueName"].ToString();
-
- if (!MessageQueue.Exists(queueName))
- {
-
-
- MessageQueue.Create(queueName, true);
- }
- using (ServiceHost host = new ServiceHost(typeof(EmpService)))
- {
- host.Open();
- Console.WriteLine("Server is up and running on port no: 57829");
- Console.WriteLine("Press any key to terminate");
- Console.ReadKey();
- host.Close();
- }
- }
- }
- }
App.Config File For the Same
- <?xml version="1.0" encoding="utf-8" ?>
- <configuration>
- <appSettings>
- <!-- Use appSetting to configure MSMQ queue name. -->
- <add key="queueName" value=".\private$\UpdateEmpTwoWay/UpdateEmp" />
- </appSettings>
- <system.serviceModel>
- <diagnostics performanceCounters="All" />
- <services>
- <service name="EmployeeUpdateReportComponent.EmpService" behaviorConfiguration="myBehavior">
-
- <!--Address attribute specifies the name of the MSMQ Queue.-->
- <endpoint name="msmqTransactionEndpoint" address="net.msmq://localhost/private/UpdateEmpTwoWay/UpdateEmp" binding="netMsmqBinding"
- bindingConfiguration="myMSMQ" contract="EmployeeUpdateReportComponent.IEmployee"/>
- <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
- <host>
- <baseAddresses>
- <add baseAddress="net.msmq://localhost/private/"/>
- <!--Both Mex and HttpBinding uses http:
- <add baseAddress="http://localhost:57829"/>
- </baseAddresses>
- </host>
- </service>
- </services>
- <bindings>
- <!--The property exactlyOnce=false means that i am using non transactional queue. The property is by default true.-->
- <netMsmqBinding>
- <binding name="myMSMQ" exactlyOnce="true" receiveErrorHandling="Move">
- <!--If we donot set the security mode to none then the following error occurs. -->
- <!-- Binding validation failed because the binding's MsmqAuthenticationMode property is set to -->
- <!-- WindowsDomain but MSMQ is installed with Active Directory integration disabled. -->
- <!-- The channel factory or service host cannot be opened.
- -->
- <security mode="Transport"/>
- </binding>
- </netMsmqBinding>
- </bindings>
- <behaviors>
- <serviceBehaviors>
- <behavior name="myBehavior">
- <serviceMetadata httpGetEnabled="true"/>
- <!--This is for enabling an exception-->
- <serviceDebug includeExceptionDetailInFaults="true"/>
- </behavior>
- </serviceBehaviors>
- </behaviors>
- </system.serviceModel>
- </configuration>
4. Now we will try to create the client service contract and the service that will help us to display/flash a status message to the client. In our case the component name would be ClientFlashStatusComponent; see:
IflashStatus.cs (ServiceContract)
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- using System.ServiceModel;
-
- namespace ClientFlashStatusComponent
- {
- [ServiceContract]
- public interface IFlashStatus
- {
- [OperationContract(IsOneWay = true)]
- void UpdateStatus(string eid, string ename, string eadd, string ecity, double esalary, string updateStatusReport);
- }
- }
FlashStatusService.cs (Service)
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- using System.ServiceModel;
- using System.Transactions;
-
- namespace ClientFlashStatusComponent
- {
- public class FlashStatusService : IFlashStatus
- {
- [OperationBehavior(TransactionScopeRequired = true, TransactionAutoComplete = true)]
- public void UpdateStatus(string eid, string ename, string eadd, string ecity, double esalary, string updateStatusReport)
- {
- Console.WriteLine("Update Status Report Of Employee Details.");
- Console.WriteLine("\n\n************************************\n");
- Console.WriteLine("Employee Updated Details are");
- Console.WriteLine("Emp ID: " + eid);
- Console.WriteLine("Name: " + ename);
- Console.WriteLine("Address: " + eadd);
- Console.WriteLine("City: " + ecity);
- Console.WriteLine("Salary: " + esalary);
- Console.WriteLine("---------------------------------------------\n Update Status\n\n");
- Console.WriteLine("Update Status: " + updateStatusReport);
- Console.WriteLine("\n==================================");
- }
- }
- }
5. And finally we will create a host for the client service that will host our client application. In our case our host name is ClientStatusFlashHost. It has a reference to our server service EmpService because from here the client will be calling the updateEmployeeDetail operationcontract and will get the response on the same screen.
FlashStatusHost.cs (Client Service Host)
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- using System.Messaging;
- using System.Transactions;
- using System.ServiceModel;
- using ClientFlashStatusComponent;
- using System.Net;
- using System.Configuration;
-
- using ClientStatusFlashHost.EmpServiceReference;
-
- namespace ClientStatusFlashHost
- {
- class FlashStatusHost
- {
- static string queueName = "";
-
- static FlashStatusHost()
- {
-
- string hostName = Dns.GetHostName();
-
-
-
-
-
-
-
-
-
- queueName = ConfigurationManager.AppSettings["queueName"].ToString();
- queueName = queueName.Replace(".", hostName);
- }
-
-
-
-
-
-
- public static int GetMessagesInQueueLength(string queueName)
- {
- MessageQueue queue = new MessageQueue(queueName);
- return queue.GetAllMessages().Length;
- }
-
-
-
-
-
-
- public static MessageQueue GetMessageQueue()
- {
- if (!MessageQueue.Exists(queueName))
-
- return MessageQueue.Create(queueName, true);
- else
- {
- MessageQueue queue = new MessageQueue(queueName);
- return queue;
- }
- }
-
- static void Main(string[] args)
- {
-
-
- using (ServiceHost host = new ServiceHost(typeof(FlashStatusService)))
- {
-
- GetMessageQueue();
-
- host.Open();
- if (GetMessagesInQueueLength(queueName) == 0)
- {
- string eid, ename, eaddress, ecity;
- double esalary;
-
- EmployeeClient objClient = new EmployeeClient("msmqTransactionEndpoint");
-
- Console.WriteLine("Enter Eid:");
- eid = Console.ReadLine();
- Console.WriteLine("Enter EName:");
- ename = Console.ReadLine();
- Console.WriteLine("Enter EAddress:");
- eaddress = Console.ReadLine();
- Console.WriteLine("Enter ECity:");
- ecity = Console.ReadLine();
- Console.WriteLine("Enter ESalary:");
- esalary = Double.Parse(Console.ReadLine());
-
- using (TransactionScope scope = new TransactionScope(TransactionScopeOption.Required))
- {
- objClient.UpdateEmployeeDetails(eid, ename, eaddress, ecity, esalary, "net.msmq://localhost/private/UpdateEmpTwoWay/UpdateStatus");
- scope.Complete();
- }
-
- objClient.Close();
-
- Console.WriteLine();
- Console.WriteLine("Wait until server Responds Or");
- Console.WriteLine("Press <ENTER> to terminate client.");
- Console.ReadLine();
-
- host.Close();
- }
- Console.ReadLine();
- }
- }
- }
- }
App.Config For the same
- <?xml version="1.0" encoding="utf-8" ?>
- <configuration>
- <appSettings>
- <add key="queueName" value=".\private$\UpdateEmpTwoWay/UpdateStatus"/>
- </appSettings>
- <system.serviceModel>
- <diagnostics performanceCounters="All" />
- <services>
- <service name="ClientFlashStatusComponent.FlashStatusService" behaviorConfiguration="myBehavior">
- <!--Address attribute specifies the name of the MSMQ Queue.-->
- <endpoint name="Operations" address="net.msmq://localhost/private/UpdateEmpTwoWay/UpdateStatus" binding="netMsmqBinding"
- bindingConfiguration="myMSMQ" contract="ClientFlashStatusComponent.IFlashStatus">
- <identity>
- <dns value="localhost"/>
- </identity>
- </endpoint>
- <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
- <host>
- <baseAddresses>
- <add baseAddress="net.msmq://localhost/private/"/>
-
- <add baseAddress="http://localhost:32578"/>
- </baseAddresses>
- </host>
- </service>
- </services>
- <bindings>
- <netMsmqBinding>
- <binding name="myMSMQ" exactlyOnce="true">
- <security mode="Transport" />
- </binding>
- <binding name="msmqTransactionEndpoint" closeTimeout="00:01:00"
- openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00"
- deadLetterQueue="System" durable="true" exactlyOnce="true" maxReceivedMessageSize="65536"
- maxRetryCycles="2" receiveErrorHandling="Fault" receiveRetryCount="5"
- retryCycleDelay="00:30:00" timeToLive="1.00:00:00" useSourceJournal="false"
- useMsmqTracing="false" queueTransferProtocol="Native" maxBufferPoolSize="524288"
- useActiveDirectory="false">
- <readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
- maxBytesPerRead="4096" maxNameTableCharCount="16384" />
- <security mode="Transport">
- <transport msmqAuthenticationMode="WindowsDomain" msmqEncryptionAlgorithm="RC4Stream"
- msmqProtectionLevel="Sign" msmqSecureHashAlgorithm="Sha1" />
- <message clientCredentialType="Windows" />
- </security>
- </binding>
- </netMsmqBinding>
- </bindings>
- <behaviors>
- <serviceBehaviors>
- <behavior name="myBehavior">
- <serviceMetadata httpGetEnabled="true"/>
- <!--This is for enabling an exception to be shown to the user which might occur in service.-->
- <serviceDebug includeExceptionDetailInFaults="true"/>
- </behavior>
- </serviceBehaviors>
- </behaviors>
- </system.serviceModel>
- </configuration>
Note: To add the reference for EmpService you will need to run the EmployeeServerUpdateHost that is responsibile for hosting the EmpService. So that the WSDL of the service can be downloaded. Now for running the EmployeeServerUpdateHost, you will need to go to the bin folder of it and double-click the .exe file of it to ensure that your console screen displays the following message:
Let it be running and now you can add the reference of your EmpService in your ClientStatusFlashHost by right-clicking on it then selecting "Add Service Reference" and type in the Address we specified in the App.Config file of our EmployeeServerUpdateHost. In other words, in our case it would be <add baseAddress="http://localhost:57829"/> our EmployeeServerUpateHost is hosted at the above address.
After downloading the service in the ClientFlashStatusHost, there will be a change in the app.config file of your ClientFlashStatusHost with the following addition of lines.
- <client>
- <endpoint address="net.msmq://localhost/private/UpdateEmpTwoWay/UpdateEmp"
- binding="netMsmqBinding" bindingConfiguration="msmqTransactionEndpoint"
- contract="EmpServiceReference.IEmployee" name="msmqTransactionEndpoint" />
- </client>
6. Now, in the same manner, you will need to add the service reference of your ClientFlashStatusHost in your EmpService Project.
For adding the reference, go to the bin folder of ClientFlashStatusHost and double-click the .exe file:
It will prompt you like the following:
Don't type anything in it, go to Visual Studio where your EmployeeUpdateReportComponent is there, right-click on the EmployeeUpdateReportComponent project and click "Add ServiceReference" then type in the WSDL path of the ClientFlashStatusHost. In our case it would be:
<add baseAddress="http://localhost:32578"/>
That is what we specified in the App.config file of ClientFlashStatusHost.
7. By following the steps above you have created a reference between your EmpService and ClientFlashStatusHost service by adding the reference of each other to each solution.Now for EmpService, ClientFlashStatusHost would be the client and for ClientFlashStatusHost EmpService would be the client. You can check this in the app.config files.
8. Before running the application kindly start the Distributed Transaction Coordinator service from services.msc.
9. We are finally done with our application, now we can test it. The output of the application can be seen in 3 ways.
- When both the server and the client are online/available.
- When the server is unavailable and the client sends a request for update (request would be stored in the server's MSMQ queue).
- Finally, when the server becomes online and the client is offline. (If there are any requests from the client in the server's MSMQ queue then they will be processed by the server and the appropriate response would be delivered to the client's MSMQ queue. Later on when the client becomes available he/she would be able to see the server response.)
Note:
When for the first time you run the application, it will create two queue in the MSMQ of your computer. You can check it by going to the following path:
open "Administrative Tools" from "Control Panel"; go to "Computer Management". Expand "Services and Applications". You fill two queues are being generated:
- updateemptwoway/updateemp (Server queue)
- updateemptwoway/updatestatus (Client Queue)
Output Phase1: Server and client are both available.
The Server is online.
The client appears and starts updating the record as per his/her id; the following screenshot displays the client's screen:
Since both are online the server screen will be displaying the following message after it receives the request from the client:
It says processing until it updates records and finally displays "Done Processing of Employee Code".
And the client screen would be getting the following message:
Since the server was online and was free, it responded quickly to the client's request and provided the status of the update to the client there and then.
Output Phase II: Server is unavailable and the client sends a request.
In this case, you will need to close the server. So that whatever request was sent by the client will be stored in the server's MSMQ queue.
Close the Server .exe file.
After closing, try to run the "ClientFlashStatuHost.exe" file by double-clicking it in the bin folder.
Since the server is unavailable, the client request would be stored in the server's MSMQ queue. The following snapshot displays that:
Now run the server exe file; you fill that the message from the server's MSMQ queue will be read by the server and it will do the required processing and send a response to the client.
If the client is available then he/she will be able to see the response or else if he/she is not available then the response message will be stored in the client's MSMQ queue. When later the client becomes online he/she will be able to see the message.
Output Phase 3: Client sends request and becomes unavailable (i.e. the server is online and the client is offline)
For this, start the client exe then type the update request and you will see the following screen (ensure that the server is not running):
Now here the client's request will be stored in the server's MSMQ queue as we saw in the output phase II. Now close the client screen and start the server.
Over here the server sent a response to the client for the same request. The response will be stored in the client MSMQ queue.
The following screenshot displays the same.
Later, when the client becomes available, the response will be displayed to the client.
Hope you found the article interesting and it may help you in your project.