Do not use remoting! While it will certainly work, Microsoft says that remoting is a legacy technology and that all new distributed applications should be developed using WCF. See here for more details.
Windows Communication Foundation (WCF) is the recommended way for two .NET processes to communicate with each other. WCF provides a unified programming model that greatly simplifies distributed development by abstracting many of the complexities associated with specific communication mechanisms, e.g., sockets, pipes, etc.
Given the details of your situation, I would suggest making each Windows service plugin a WCF service. For each WCF service, i.e., plugin, define the interface that it needs to expose to your UI. The interface is simply a C# interface adorned with the ServiceContract attribute. This interface contains the methods, each of which is adorned with the OperationContract attribute, that your UI will use to communicate with the WCF service (plugin). These methods can accept and return any serializable .NET type or, as is often the case, your own custom types. To use custom types with WCF, simply decorate them with the DataContract attribute and mark the members that you want to be exchanged via WCF with the DataMember attribute.
Once you have your ServiceContract interface defined, define a class that implements that interface. Each OperationContract method does whatever it needs to do, e.g., interact with database, calculate some value, etc. Once you've done this, you have effectively defined a WCF service. Here's a short, but working, example:
using System.ServiceModel;
namespace AdditionServiceNamespace
{
[DataContract]
public class Complex
{
[DataMember]
public int real;
[DataMember]
public int imag;
}
[ServiceContract]
public interface IAdditionService
{
[OperationContract]
Complex Add(Complex c1, Complex c2);
}
public class AdditionService : IAdditionService
{
public Complex Add(Complex c1, Complex c2)
{
Complex result = new Complex();
result.real = c1.real + c2.real;
result.imag = c1.imag + c2.imag;
return result;
}
}
}
The next step is to host this WCF service so that it is available to be used by your UI. Since you will be using a Windows service, hosting your WCF service is done easily enough in the OnStart()
callback of your Windows service, like so:
using System.ServiceModel;
using System.ServiceProcess;
using AdditionServiceNamespace;
namespace WindowsServiceNamespace
{
public class WindowsService : ServiceBase
{
static void Main()
{
ServiceBase[] ServicesToRun = new ServiceBase[]
{ new WindowsService() };
ServiceBase.Run(ServicesToRun);
}
private ServiceHost _host;
public WindowsService()
{
InitializeComponent();
}
protected override void OnStart(string[] args)
{
_host = new ServiceHost(typeof(AdditionService));
_host.Open();
}
protected override void OnStop()
{
try
{
if (_host.State != CommunicationState.Closed)
{
_host.Close();
}
}
catch
{
// handle exception somehow...log to event viewer, for example
}
}
}
}
The only thing left to do is to define an app.config file for your Windows service that will configure certain required aspects of your WCF service. This may seem like overkill, but keep two things in mind. First of all, Visual Studio gives you a basic app.config file automatically when you add a WCF service class to your project. Second, the app.config file gives you a tremendous amount of control over your WCF service without requiring changes to the code. Here's the companion app.config file for the example above:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<services>
<service name="AdditionServiceNamespace.MyAdditionService"
behaviorConfiguration="default">
<endpoint name="AdditionService"
address="net.pipe://localhost/AdditionService"
binding="netNamedPipeBinding"
contract="AdditionServiceNamespace.IAdditionService" />
<endpoint address="net.pipe://localhost/AdditionService/MEX"
binding="mexNamedPipeBinding"
contract="IMetadataExchange" />
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="default>
<serviceMetadata />
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
</configuration>
Note that the AdditionService WCF service has two endpoints. The metadata exchange endpoint is used for code generation by the client, so ignore it for now. The first endpoint is configured to use the NetNamedPipeBinding. This is the binding to use if your UI and Windows service will be running on the same machine (see here for a flowchart on selecting the appropriate binding to use). However, this binding cannot be used if your UI and Windows service will be running on different machines. In that case, you could use the NetTcpBinding as a replacement. To substitute the NetTcpBinding for the NetNamedPipeBinding, you would simply need to change the address and binding of the endpoint, like this:
<endpoint name="AdditionService"
address="net.tcp://<machine hostname here>/AdditionService"
binding="netTcpBinding"
contract="AdditionServiceNamespace.IAdditionService" />
No code changes are required! Make the change, restart your service, and your WCF service is now available to remote machines. You can even allow multiple endpoints for the same WCF service if you so desired. The point is, the app.config file offers a tremendous amount of flexibility without requiring changes to the code.
That's it! You now have a WCF service hosted inside your Windows service available for use by your UI.
So how does the UI side, i.e., the client side, work?
This is where the real power of WCF comes into play. When getting started with WCF, the easiest thing to do is leverage Visual Studio's code generation capabilities. Make sure that your Windows service (the one hosting the AdditionService) is running. In your UI project, right-click on your project in the Solution Explorer and select the Add Service Reference... menu option. In the Address box, type net.pipe://localhost/AdditionService
, and click the Go button. You should see the AdditionService show up in the Services list. In the Namespace box, type AdditionService
and click the OK button.
Performing these steps will generate a client proxy and a properly defined app.config file that are added to your UI project. This client proxy becomes your client-side AdditionService API, and you use it like this:
using TestConsoleApp.AdditionService;
namespace TestConsoleApp
class Program
{
static void Main(string[] args)
{
AdditionServiceClient client = new AdditionServiceClient();
Complex c1 = new Complex(), c2 = new Complex();
c1.real = 3; c1.imag = 5;
c2.real = 1; c2.imag = 7;
Complex result = client.Add(c1, c2);
}
}
}
Notice how simple this is. Basically, a client proxy, AdditionServiceClient
, is instantiated. Then two Complex
objects are created. Finally, the Add()
method on the client proxy is invoked, and a Complex
result is returned.
What is going on behind the scenes is that the Add()
method of the client proxy is actually passing the two Complex
objects to the AdditionService WCF service hosted in the Windows service. The AdditionService performs the addition and then returns the result. All of this happens over a named pipe, but notice that there is no named pipe-specific code here at all! WCF has abstracted all of that complexity behind a programming model that is defined by the IAdditionService interface.
I know this is a lot of information to digest, but I hope it is evident just how powerful and easy-to-use WCF can be. Of course, this example only hits a small subset of everything that is available within WCF.
In the end, though, WCF should be the mechanism you use to communicate between your UI and your Windows service. For more information, I would highly recommend Juval Lowy's book Programming WCF Services for all things WCF. You can also visit his website, IDesign.net, for free WCF code samples. For more introduction to WCF, watch this free video at dnrTV. It covers the purpose of WCF and demonstrates WCF programming through some easy-to-follow examples.