Wednesday 19 August 2015

Using MSMQ with WCF in C# & VB

You might come across a situation where data doesn't need to be process immediately or the data can be processed at a later time. There might be also a situation where you need to pass the data to another client that are currently offline or not available. In such a case, you opt for MSMQ to deal with these situations. Before continue reading, you need to have knowledge on what MSMQ is all about. So, if you are new to MSMQ, do take a look on the following link https://msdn.microsoft.com/en-us/library/ms711472(v=vs.85).aspx.

Now I'm going to show you how to implement MSMQ with WCF. This means that the application will send and receive message from the queue through service. Other than that, transactional queue will be used to perform retry on the queue message.

Firstly, implement a service and contract for receiving and processing the message in the queue. The parameter TransactionScopeRequired in the OperationBehavior needs to be set to true. This is to ensure that whenever there's an exception that prevents the method from complete, it will go back to the queue. For more detail about Transacted MSMQ binding, you can check it out from here https://msdn.microsoft.com/en-us/library/ms751493(v=vs.110).aspx.

[C#]
[ServiceContract]
public interface IRecoveryService
{
    [OperationContract(IsOneWay = true)]
    void Log(string value);
}

[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall, ConcurrencyMode = ConcurrencyMode.Single)]
public class RecoveryService : IRecoveryService
{
    [OperationBehavior(TransactionScopeRequired = true)]
    public void Log(string value)
    {
        // Do your stuff
    }
}

[VB]
<ServiceContract>
Public Interface IRecoveryService
    <OperationContract(IsOneWay:=True)>
    Sub Log(ByVal value As String)
End Interface

<ServiceBehavior(InstanceContextMode:=InstanceContextMode.PerCall, ConcurrencyMode:=ConcurrencyMode.Single)>
Public Class RecoveryService
    Implements IRecoveryService

    <OperationBehavior(TransactionScopeRequired:=True)>
    Public Sub Log(value As String) Implements IRecoveryService.Log
        ' Do your stuff
    End Sub
End Class


The following are the sample configuration for hosting msmq with WCF. Assuming that my queue is a transactional private queue with the name wcfmsmq and my msmq service name is RecoveryService.

<system.serviceModel>
  <serviceHostingEnvironment multipleSiteBindingsEnabled="true">
    <serviceActivations>
      <add factory="System.ServiceModel.Activation.ServiceHostFactory" relativeAddress="./RecoveryService.svc" service="WCFMSMQVB.Services.RecoveryService" />
    </serviceActivations>
  </serviceHostingEnvironment>
  <services>
    <service name="WCFMSMQVB.Services.RecoveryService" behaviorConfiguration="DefaultServiceBehavior">
      <endpoint name="netMsmqRecoveryService" address="net.msmq://localhost/private/wcfmsmq" binding="netMsmqBinding" bindingConfiguration="netMsmq" contract="WCFMSMQVB.Services.Contracts.IRecoveryService" />
      <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
    </service>
  </services>
  <behaviors>
    <serviceBehaviors>
      <behavior name="DefaultServiceBehavior">
        <serviceMetadata httpGetEnabled="true" />
        <serviceDebug includeExceptionDetailInFaults="true" />
      </behavior>
    </serviceBehaviors>
  </behaviors>
  <bindings>
    <netMsmqBinding>
      <binding name="netMsmq" maxReceivedMessageSize="2147483647" maxRetryCycles="3" retryCycleDelay="00:00:05" receiveErrorHandling="Drop">
        <security mode="None">
          <transport msmqAuthenticationMode="None" msmqProtectionLevel="None" />
        </security>
      </binding>
    </netMsmqBinding>
  </bindings>
  <client>
    <endpoint address="net.msmq://localhost/private/wcfmsmq" binding="netMsmqBinding"
              bindingConfiguration="netMsmq" contract="RecoveryService.IRecoveryService"
              name="netMsmqRecoveryService" />
  </client>
</system.serviceModel>

If you tried to run your project with IIS Express and call the msmq service, you will hit with this error "The protocol 'net.msmq' is not supported.". This is because IIS Express does not support net.pipe protocol. You need to host them to your IIS. For more detail about IIS Express, you can check out from the following link http://www.iis.net/learn/extensions/introduction-to-iis-express/iis-express-faq.

When deploying your msmq service to IIS, make sure that your web site contain the net.msmq binding and the Enabled Protocols contains net.msmq. Otherwise you will get hit with the error message "The protocol 'net.msmq' is not supported."

To simplify the implementation to call msmq service, add a service reference (in this case is RecoveryService) to your project. Then call the service's method and the data will be sent to msmq. If you noticed that the data is in the queue and did not get process, this is due to your msmq service is not active. Just invoke the msmq service url and it will become active and process the queue message.

[C#]
var proxy = new RecoveryServiceClient();

try
{
    proxy.Log(value);
}
finally
{
    proxy.Close();
}

[VB]
Dim proxy As RecoveryServiceClient = New RecoveryServiceClient()

Try
    proxy.Log(value)
Catch ex As Exception
    proxy.Close()
End Try


For the binding value configured in netMsmqBinding section in the sample configuration defined above:
maxReceivedMessageSize - To specify how large the data is allowed to be sent through WCF service,
maxRetryCycles - Define the number of times to try before performing the action specified at receiveErrorHandling attribute,
retryCycleDelay - The amount of waiting time before performing the next retry,
receiveErrorHandling - The action to be done after reached the maximum number of retry as specified in maxRetryCycles attribute.
For more details on what other attributes can be configured to the netMsmqBinding section can be found here https://msdn.microsoft.com/en-us/library/ms731380(v=vs.110).aspx.

So, based on the values specified, if the method fail to process, it will place the data back to the queue and wait for 5 seconds before trying to process the data again. After retried for 3 times, which is the maximum number of retry as configured, MSMQ will remove the data from the queue. The reason why it is being removed from the queue is because Drop is specified in the receiveErrorHandling attribute.

If you do not want the message to be removed from the queue after reached the maximum number of retry attempt, you can consider moving the poison message to the sub-queue by assigning value Move to the receiveErrorHandling attribute.

For more details on handling poison message or to move them into poison message sub-queue and handle the message in the sub-queue, you can refer to https://msdn.microsoft.com/en-us/library/aa395218(v=vs.110).aspx.

Here's the sample code that are developed in layered by following http://serena-yeoh.blogspot.com/2014/03/layered-architecture-solution-guidance.html: C#: https://onedrive.live.com/redir?resid=E6612168B803803D!356&authkey=!AESnIsw8nRxITpY&ithint=file%2czip
VB: https://onedrive.live.com/redir?resid=E6612168B803803D!357&authkey=!AHKlSmIMOkYn-nQ&ithint=file%2czip

Read further if you want to run the sample project.

To run the sample, create a transactional msmq with the name wcfmsmq and publish the project that ends with Hosts.Web to your IIS, since IIS Express does not support net.msmq protocol.

To call the method to send data to MSMQ with WCF Service, you can either call it using the WCFTestClient application or run the project that ends with UI.Web and hit the button that displayed on the page. Do make sure to change the endpoint address in the configuration file located in the project that ends with UI.Web and point it to the project that you had published to IIS before hitting on the button.

Once you hit the button, it will assume that the method had failed and send the data to the queue. To simulate the retry mechanism until the MSMQ drop the data from the queue, the method that handles the MSMQ data will check for the text file existence and throw exception if is doesn't exist.

If you want the queue to process the data successfully, change the appSettings' logPath in the hosts.web to your desired location and create a folder name "SampleLog" with the extension ".txt". Or if you prefer to have your own implementation, just change the method "Log" implementation to your desired behavior in the RecoveryComponent file.




No comments:

Post a Comment