A named pipe itself is just an object in the windows kernel. It provides access for different clients to a shared resource. A pipe is identified by its name and can be used for one-way or duplex communication scenarios. While there may be only one pipe server, there may be more than one client applications using a pipe. With the NetNamedPipeBinding, WCF offers an abstraction to pipe objects, making it really easy to use them for inter-process communication.
I created a simple example to show you the setup of two applications communicating via named pipes. Let’s assume that one application – the pipe server – can be used to add products and stores new product information locally. Another application on the same machine needs to know when a new product was added and eventually needs to retrieve further product information. We start by setting up the contract both applications will use:
[ServiceContract(SessionMode = SessionMode.Required,
CallbackContract = typeof(IProductServiceCallback))]public interface IProductService
{
[OperationContract]
void RegisterForAddedProducts();
[OperationContract]
void GetProductInformation(string productNumber);
}
The service contract contains two methods. The first one can be used by the client to register itself for notification of new products. The second method can be used to request further product information by passing the product number. Both methods do not have a return value. In order to pass information back to the client, we set the SessionMode and CallbackContract properties of the ServiceContract attribute. The callback contract defines the methods that must be present on the client calling the service in order to get messages from the server. Let’s have a look at the callback contract:
public interface IProductServiceCallback
{
[OperationContract(IsOneWay = true)]
void ProductAdded(string productNumber);
[OperationContract(IsOneWay = true)]
void ProductInformationCallback(Product product);
}
Not surprisingly, the callback contract also contains two methods. ProductAdded gets called by the server, when a new product was added. The ProductInformationCallback will be called from the server after the client requested further information and the server had enough time to retrieve them. Instead I could just have defined a return value in the service to pass the product back to the client but this scenario shows a great benefit of the callback mechanism. It’s asynchronous by default and therefore doesn’t freeze the client and gives the server enough time to process the request. This is ideal for longer taking requests. Notice that both methods are marked as one way. The second method uses Product as parameter which is no built in CLR object. Here is the very short definition of a product:
public class Product
{
string Number { get; set; }
string Name { get; set; }
decimal Price { get; set; }
string Manufacturer { get; set; }
}
That’s all for the contract. Let’s go on with the pipe server.
The Server
The server needs to implement the service contract:
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
public class ProductService : IProductService
The service behavior needs to specify the instance context mode as single. This together with the session mode on the contract is required to support duplex communication. But there is another reason. I’d like to pass an instance of the host application to the service implementation in order to pass calls from a service request to the application that can handle the request and provide appropriate information. Therefore we define the following constructor for the service implementation:
public ProductService(IHostApplication hostApplication)
{
addedProductsCallbackList = new List<IProductServiceCallback>();
this.hostApplication = hostApplication;
this.hostApplication.ProductAdded += HostApplicationProductAdded;
}
The host application is indicated by a simple interface. It defines an event to notify listeners when a new product was added. The service itself holds a list of callbacks for registered clients. When a new product was added the service iterates of the callback list and invokes the ProductAdded method passing it the product number:
void HostApplicationProductAdded(object sender, EventArgs<string> e)
{
foreach (var productServiceCallback in addedProductsCallbackList)
{
productServiceCallback.ProductAdded(e.Data);
}
}
The client subscribes itself through the RegisterForAddedProducts method of the service:
public void RegisterForAddedProducts()
{
var callback = OperationContext.Current.
GetCallbackChannel<IProductServiceCallback>();
GetCallbackChannel<IProductServiceCallback>();
if (callback != null)
{
this.addedProductsCallbackList.Add(callback);
}
}
}
You can retrieve a callback channel by calling the generic GetCallbackChannel method of the current instance of the OperationContext. The context contains information regarding the current service call. If you want to see the GetProductInformation method, please download the sample code. I will leave it out right now.
What we now need to do is to start the service. Let’s first have a look at the endpoint definition from the configuration file:
<system.serviceModel>
<services>
<service name="IProductService">
<endpoint address="net.pipe://localhost/ProductService"
binding="netNamedPipeBinding"
contract="WCFNamedPipesExample.Services.IProductService"
name="ProductServiceEndpoint" />
</service>
</services>
</system.serviceModel>
This is a very simple configuration. No additional behaviors or anything else. We can use this configuration to start the service from our host application:
private void StartHost()
{
var group =
ServiceModelSectionGroup.GetSectionGroup(
ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None));
if (group != null)
{
var service = group.Services.Services[0];
var baseAddress = service.Endpoints[0].Address.AbsoluteUri.
Replace(service.Endpoints[0].Address.AbsolutePath, String.Empty);
var productService = new ProductService(this);
var host = new ServiceHost(productService, new[] { new Uri(baseAddress) });
host.AddServiceEndpoint(typeof(IProductService),
new NetNamedPipeBinding(),
service.Endpoints[0].Address.AbsolutePath);
service.Endpoints[0].Address.AbsolutePath);
try
{
host.Open();
}
catch (CommunicationObjectFaultedException cofe)
{
//log exception or do something appropriate
}
catch (TimeoutException te)
{
//log exception or do something appropriate
}
}
}
In this case there is only one service with only one endpoint. So we could directly access the first element in the collections. You could also iterate over services and endpoint to create the objects and hosts you need. We start the listening on the pipe endpoint by calling host.Open(). It’s always a good idea to do this inside a try/catch block.
The Client
With the setup so far we have a running host listening for requests from a client. So let’s move on to the realization a simple client. The client is another WPF application running on the same machine. I will start by defining the client endpoint in the configuration file:
<system.serviceModel>
<client>
<endpoint address="net.pipe://localhost/ProductService"
binding="netNamedPipeBinding"
contract="WCFNamedPipesExample.Services.IProductService"
name="ProductServiceEndpoint" />
</client>
</system.serviceModel>
As you can see, it’s nearly the same as for the service description on the server side. With the configuration we can now create a pipe proxy in our client application in order to call server side methods. To get messages back from the server we also need to implement the callback interface:
public class ClientController : IProductServiceCallback
As you can see, it’s nearly the same as for the service description on the server side. With the configuration we can now create a pipe proxy in our client application in order to call server side methods. To get messages back from the server we also need to implement the callback interface:
public class ClientController : IProductServiceCallback
The creation of the pipe proxy is done in the following method:
private void Connect()
{
var group = ServiceModelSectionGroup.GetSectionGroup(
ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None));
if (group != null)
{
//create duplex channel factory
pipeFactory = new DuplexChannelFactory<IProductService>(
this, group.Client.Endpoints[0].Name);
this, group.Client.Endpoints[0].Name);
//create a communication channel and register for its events
pipeProxy = pipeFactory.CreateChannel();
((IClientChannel) pipeProxy).Faulted += PipeProxyFaulted;
((IClientChannel) pipeProxy).Opened += PipeProxyOpened;
try
{
//try to open the connection
((IClientChannel) pipeProxy).Open();
this.checkConnectionTimer.Stop();
//register for added products
pipeProxy.RegisterForAddedProducts();
}
catch
{
//for example show status text or log;
}
}
}
To create a duplex channel we can use the generic constructor of the DuplexChannelFactory. The constructor takes an instance of the callback contract, which is in this case the client controller itself, and the name of an endpoint. With the pipe factory you can then create a concrete channel. Subscribe to the channel events to notify the user if the channel was unexpectedly closed or to start recovery scenarios. Within the try/catch block we open the channel and register ourselves to the server to get notified of new products. The client uses a timer (checkConnectionTimer) which repeatedly tries to connect to the server until a connection was established. So, even if the server is not up the client will attempt to connect. As soon as the server starts the client can automatically connect.
When a new product was added the server calls the ProductAdded method on the client passing it the product number. In the sample application I just show a message box with the product number:
public void ProductAdded(string productNumber)
{
MessageBox.Show("Received Product Number: "
+ productNumber, "Product Added!");
}
Again I will leave out the product information part. You can check it out in the sample code. That’s all! With this setup you get a duplex pipe channel with a client calling asynchronous methods on the server and a server using callbacks to invoke methods on the client passing the requested information. Here is a screenshot of the sample server and client applications with the product added callback invoked.
Both applications are WPF applications. WPF application architecture was not in the focus of this article, so I made it as simple as possible. Please don’t blame me on not using MVVM or the like.
Download Sample Code:
Download Sample Code: