Use Windows Communication Foundation (WCF) with named pipe bindings.
Before
WCF, you had many, many communications choices, including COM, DCOM,
.NET Remoting, SOAP, TCP/IP, HTTP, named pipes, and more. WCF wraps all
of those into a single framework. It separates what you communicate from how you communicate. As you’ll see, you can change from using named pipes to HTTP with only configuration changes.
WCF
was designed from the ground up to unify communication technologies
under a single framework. This allows your apps to be resilient to
change when you need to modify how they communicate. In addition, you
get an enormous amount of supporting abilities like security, auditing,
extensibility, and more. If you need to use more than one protocol, you
can interact with both of them through the common interface of WCF
rather than worrying about protocol specifics. WCF can be in
command-line apps, GUIs, Windows services, or IIS components.
WCF is a large enough topic for its own book (or series of books), but we’ll present enough to get you started.
A WCF app generally has three components:
A
service interface and implementation. This can be implemented in the
same assembly as the server, but it can be useful to keep it in a
separate assembly so that it can be used in multiple hosts if needed.
A server that hosts the service implementation.
A client that calls the server via a proxy class that implements the library’s interfaces.
This section will handle each component in turn.
Define the Service Interface
The
service in this example will be a simple file server that implements
three methods to retrieve directory and file data for the client. The
methods are defined in an interface inside a class library that the
server references and implements.
using System;
using System.ServiceModel;
namespace FileServiceLib
{
[ServiceContract]
public interface IFileService
{
[OperationContract]
string[] GetSubDirectories(string directory);
[OperationContract]
string[] GetFiles(string directory);
[OperationContract]
int RetrieveFile(string filename, int amountToRead,
out byte[] bytes);
}
}
The attributes on the interface and methods tell WCF that these should be part of the service.
The implementation is simple:
using System;
using System.ServiceModel;
using System.IO;
namespace FileServiceLib
{
public class FileService : FileServiceLib.IFileService
{
//just because it's a file service doesn't mean that you have to
//actually use the real underlying file system--you could make
//your own virtual file system
public string[] GetSubDirectories(string directory)
{
return System.IO.Directory.GetDirectories(directory);
}
public string[] GetFiles(string directory)
{
return System.IO.Directory.GetFiles(directory);
}
public int RetrieveFile(string filename, int amountToRead,
out byte[] bytes)
{
bytes = new byte[amountToRead];
using (FileStream stream = File.OpenRead(filename))
{
return stream.Read(bytes, 0, amountToRead);
}
}
}
}
See the FileServiceLib project in the accompanying source code.
Create the Server
In
this case, the server is going to be a console application for
simplicity. You could just as easily make it part of a Windows service
or run it under IIS.
using System;
using System.ServiceModel;
using FileServiceLib;
namespace WCFHost
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("FileService Host");
//tell WCF to start our service using the
//info in app.config file
using (ServiceHost serviceHost =
new ServiceHost(typeof(FileServiceLib.FileService)))
{
serviceHost.Open();
Console.ReadLine();
}
}
}
}
The
configuration file is where the action is. In this case, it tells WCF
to use named pipes to communicate with clients, which is ideal for
processes located on the same machine.
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<services>
<service name="FileServiceLib.FileService"
behaviorConfiguration="FileServiceMEXBehavior">
<endpoint address=""
binding="netNamedPipeBinding"
contract="FileServiceLib.IFileService"/>
<!-- a MEX endpoint allows WCF to exchange
metadata about your connection -->
<endpoint address="mex"
binding="mexNamedPipeBinding"
contract="IMetadataExchange"/>-->
<host>
<baseAddresses>
<add baseAddress="net.pipe://localhost/FileService"/>
</baseAddresses>
</host>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="FileServiceMEXBehavior">
<serviceMetadata />
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
</configuration>
See the WCFHost project in the accompanying source code.
Create the Client
To allow the client to communicate with the server seamlessly, without any knowledge of the underlying protocols, you need a proxy
class, which converts normal method calls into WCF commands that
eventually translate into bytes that go over the connection to the
server. You could create this class by hand, but it’s easier to start
up the server and run a utility to generate it for you.
The
file svcutil.exe ships with the Windows SDK. It generates two files: a
C# code file with the proxy class implementation, and a configuration
file to use in the client.
My
copy of svcutil.exe is located in C:\Program Files (x86)\Microsoft
SDKs\Windows\v7.0A\bin\. Your location may vary. After starting the
server, I ran the following command at the console:
D:\>"C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\bin\SvcUtil.exe " net.pipe://localhost/FileService /out:FileServiceLib_Proxy.cs/config:app.config
Note
Visual
Studio can also generate a proxy class for you in the Add Service
Reference dialog box. However, if you have multiple developers working
on a project, you should stick to using svcutil.exe because Visual
Studio generates additional metadata files that also need to be checked
in, but these can be different on different machines, which can cause
headaches. It’s better to just avoid it completely.
Note
The
proxy is generated for the interface and is the same regardless of
which protocol is used. This allows you to make protocol changes in the
configuration without having to rebuild the application.
In this example, the client will be a WinForms app that allows you to call each service method and then shows the results.
First, add both of the svcutil.exe-generated files to the project. The app.config file looks similar to this:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<system.serviceModel>
<bindings>
<netNamedPipeBinding>
<binding name="NetNamedPipeBinding_IFileService"
closeTimeout="00:01:00"
openTimeout="00:01:00" receiveTimeout="00:10:00"
sendTimeout="00:01:00"
transactionFlow="false" transferMode="Buffered"
transactionProtocol="OleTransactions"
hostNameComparisonMode="StrongWildcard"
maxBufferPoolSize="524288"
maxBufferSize="65536" maxConnections="10"
maxReceivedMessageSize="65536">
<readerQuotas maxDepth="32"
maxStringContentLength="8192"
maxArrayLength="16384"
maxBytesPerRead="4096"
maxNameTableCharCount="16384" />
<security mode="Transport">
<transport protectionLevel="EncryptAndSign" />
</security>
</binding>
</netNamedPipeBinding>
</bindings>
<client>
<endpoint address="net.pipe://localhost/FileService"
binding="netNamedPipeBinding"
bindingConfiguration
= "NetNamedPipeBinding_IFileService"
contract="IFileService"
name="NetNamedPipeBinding_IFileService">
<identity>
<userPrincipalName value="Ben-Desktop\Ben" />
</identity>
</endpoint>
</client>
</system.serviceModel>
</configuration>
The proxy class has the name FileServiceClient, so to instantiate and use it looks something like this:
using System;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace WCFClient
{
public partial class Form1 : Form
{
FileServiceClient fsClient = null;
public Form1()
{
InitializeComponent();
fsClient = new FileServiceClient();
}
private void buttonGetSubDirs_Click(object sender, EventArgs e)
{
SetResults(fsClient.GetSubDirectories(
textBoxGetSubDirs.Text));
}
private void buttonGetFiles_Click(object sender, EventArgs e)
{
SetResults(fsClient.GetFiles(textBoxGetFiles.Text));
}
private void buttonGetFileContents_Click(object sender,
EventArgs e)
{
int bytesToRead = (int)numericUpDownBytesToRead.Value;
byte[] buffer = new byte[bytesToRead];
int bytesRead =
fsClient.RetrieveFile(out buffer,
textBoxRetrieveFile.Text,
bytesToRead);
if (bytesRead > 0)
{
//just assume ASCII for this example
string text = Encoding.ASCII.GetString(buffer,
0,
bytesRead);
SetResults(text);
}
}
private void SetResults(string[] results)
{
//use LINQ to concat the results easily
textBoxOutput.Text =
results.Aggregate((a, b) => a + Environment.NewLine + b);
}
private void SetResults(string results)
{
textBoxOutput.Text = results;
}
}
}
Figure 1 shows the client running on the same machine as the server.