Creating LAN messenger using WCF Message Inspector
WCF self-hosting service can be really useful for messaging within the single or multiple tiers. Communication may involve sending execution tasks to other components or sending simple messages to be displayed on remote computer.
When sending system massages we can use Message Queuing (MSMQ) and transactions to ensure that the task will be executed even if the service if temporary off-line by storing it in the queue for planned execution. This approach makes the system loosely coupled to it’s components.
Self hosted WCF service can also be used to send and receive simple messages. In this article I will show you how to implement simple LAN messenger.
The interesting feature of WCF services is the ability to intercept messages as they arrive through the message channel. By using Message Inspector we can preview or change the message that we have intercepted.
We can hook in our Message Inspector by creating and adding custom service behavior. Lets start with the MyServiceBehavior class:
public class MyServiceBehavior : IServiceBehavior { public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, System.Collections.ObjectModel.Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters) { } public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase) { //note that ChannelDispatchers are available only after service is open foreach (ChannelDispatcher channelDispatcher in serviceHostBase.ChannelDispatchers) { foreach (EndpointDispatcher epDisp in channelDispatcher.Endpoints) { //attach message interceptor epDisp.DispatchRuntime.MessageInspectors.Add(new MyMessageInspector()); } } } public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase) { } }
Now we need to create Message Interceptor class that will intercept the massages and in our case send it to the UI to be displayed. Code below simply extracts the message value from the SOAP message received.
public class MyMessageInspector : IDispatchMessageInspector { public object AfterReceiveRequest(ref System.ServiceModel.Channels.Message request, IClientChannel channel, InstanceContext instanceContext) { foreach (var form in Application.OpenForms) { if (form is ClientServiceForm) { var mainForm = form as ClientServiceForm; var message = request.ToString(); //simple string extraction (we assume only one interface method) var startPos = message.LastIndexOf("<value>"); var endPos = message.LastIndexOf("</value>"); message = message.Substring(startPos + 7, endPos - startPos - 7); mainForm.AddMessage("\n" + message, ""); //send message to be displayed return null; } } return null; }
Next step is to start self-hosted service when application starts. Code below creates ServiceHost instance, adds TCP endpoint and our custom behavior class. Please note that service will remain open as long as the window Form is not closed (Windows Service wcf host should be implemented instead if required).
void StartService() { var baseAddress = new Uri(HostEndPointAddress); hostService = new ServiceHost(typeof(MessageService), baseAddress); hostService.AddServiceEndpoint(typeof(IServiceHost), new NetTcpBinding(), ""); hostService.Description.Behaviors.Add(new WCFClient.AppService.MyServiceBehavior()); hostService.Open(); txtClientReceivedMessage.AppendText(string.Format("The Service started at {0}", baseAddress)); }
Now is the time to implement sending messages to the remote endpoint. We will use ChannelFactory to instantiate new service that will send the messages. Please note that we have to send it to other thread in order to avoid our main UI thread to function properly.
private void btnSendMessage_Click(object sender, EventArgs e) { if (txtClientInputMessage.Text.Trim().Length == 0) { MessageBox.Show("Enter message to be sent!"); return; } //send it to other thread var wcfChannelThread = new Thread(new ParameterizedThreadStart(SendMessage)); wcfChannelThread.Start(new List<string>() { txtClientInputMessage.Text, txtTargetEndPoint.Text }); txtClientInputMessage.Text = "";//clear input box } void SendMessage(object config) { var arg = config as List<string>; try { //use TCP when sending message on the same computer or within the trusted network var binding = new NetTcpBinding(); var endpoint = new EndpointAddress(arg[1]); var factory = new ChannelFactory<IServiceHost>(binding, endpoint); var channel = factory.CreateChannel(); //send entered message using created channel channel.SendMessage(Convert.ToString(arg[0])); //send message to UI AddMessage("", arg[0]); } catch (Exception ex) { MessageBox.Show(ex.Message); } }
The last thing to do is to create thread save function that will update our UI rich-text box control with the messages coming in from background threads:
public void AddMessage(string messageReceived, string messageSent) { if (this.InvokeRequired) { BeginInvoke(new AddMessageDel(AddMessage), new object[] { messageReceived, messageSent }); } else { //insert message if (messageReceived.Trim().Length > 0) { txtClientReceivedMessage.SelectionFont = new System.Drawing.Font("Tahoma", 11); txtClientReceivedMessage.SelectionColor = Color.Red; txtClientReceivedMessage.AppendText(string.Format("{0}", messageReceived)); } else { //sent by me txtClientReceivedMessage.SelectionFont = new System.Drawing.Font("Tahoma", 11); txtClientReceivedMessage.SelectionColor = Color.Green; txtClientReceivedMessage.AppendText(string.Format("\nme: {0}", messageSent)); } //insert date txtClientReceivedMessage.SelectionFont = new System.Drawing.Font("Tahoma", 8); txtClientReceivedMessage.SelectionColor = Color.Blue; txtClientReceivedMessage.AppendText(string.Format(" [sent at: {0}]", DateTime.Now.ToString("HH:mm:ss"))); //scroll to bottom txtClientReceivedMessage.SelectionStart = txtClientReceivedMessage.Text.Length; txtClientReceivedMessage.ScrollToCaret(); } }
I have included two separate project solutions. Each project represents client endpoint that can be used to send message to the other. When using the code, firewall warning message could be displayed – please allow it to listen on chosen port.
If you want to send a message to other computer within the local network, please change the endpoint address to following format: net.tcp://endpointComputerIP:82/client2. If you want to send message to other computer outside of your network, please change binding to http or https.
Hope I have inspired you to explore all possibilities of WCF framework 🙂