I am fascinated with Windows Communication Foundation (WCF) but the configuration files can be cumbersome. Until now I haven't had the time to investigate programmatic configuration of WCF, but I may be involved in a project soon in which WCF would be useful but where configuration files would present an unnecessary complication to the end user. So I've spent some time researching and experimenting. To my happy surprise, WCF is easily configured programmatically. A little research has resulted in a simple Windows Forms application talking to a Windows Service via WCF while both client and server use a common class library that defines the service contract and data transfer objects. I'll give you a brief overview and then you can download the solution here.
First let me give credit where credit is due. I could not have figured out all of this stuff without the great help of Juval Lowy and his book Programming WCF Services. I highly recommend the book. I also took advantage of some of the samples published on his http://www.idesign.net/ web site. If you need help with your WCF work, I urge you to contact them.
You download the entire solution at the bottom of this entry, so I'm not going to push all the code into the text here, but I'll try to cover the highlights.
Framework/Architecture
The architecture of this little test application is simple.
- MyWcfService - (server) A Windows service application
- MyWcfMon - (client) A Windows Forms application with a simplistic test
- MyWcfLib - A class library defining the service contract used by client and server
MyWcfLib
Since I like the "contract first" approach, let's begin with the interface and data transfer objects shared by client and server. This is a very simple piece of code but sufficient to illustrate the principles involved (see IMyWcfServiceControl.cs).
[ServiceContract]
public interface IMyWcfServiceControl
{
[OperationContract]
MyWcfServiceResponse ExecuteCommand(MyWcfServiceCommand command);
}
[DataContract]
public class MyWcfServiceCommand
{
[DataMember]
public string Command { get; set; }
}
[DataContract]
public class MyWcfServiceResponse
{
[DataMember]
public string Response { get; set; }
}
As you can see, we have a simple service contract with a single operation and a simple command object taken and response object returned.
You'll also notice an Extensions.cs file in the MyWcfLib project. These are a few of my favorite extensions I've written lately. I find them very useful but they are not terribly relevant to this project. You can take a look at them later in the source download.
MyWcfService
I won't bore you with the details of the Windows service code, though I do encourage you to download the code and give it a look. I've blogged about this before but it's worth metioning, if only for the keyword exposure. The service code allows you to debug the service as a console application and then compile in "Release" and deploy/install as a real Windows service. A handy little chunk of code.
The primary area of interest in the service is the declaration of the WCF ServiceHost and how it is configured in code. In the following lines of code found in the MyWcfService.cs file in the project, we create a WCF host listening on a port configured using the m_port value, requiring Windows authentication and using encryption and message signing on the transport layer. Fast but highly secure.
Uri tcpBaseAddress = new Uri(string.Format("net.tcp://localhost:{0}/", m_port));
serviceHost = new ServiceHost(typeof(MyWcfServer), tcpBaseAddress);
NetTcpBinding tcpBinding = new NetTcpBinding(SecurityMode.Transport, false);
tcpBinding.Security.Transport.ClientCredentialType = TcpClientCredentialType.Windows;
tcpBinding.Security.Transport.ProtectionLevel = ProtectionLevel.EncryptAndSign;
serviceHost.AddServiceEndpoint(typeof(IMyWcfServiceControl), tcpBinding,
string.Format("net.tcp://localhost:{0}/MyWcfServiceControl", m_port));
//this is the default but good to know
serviceHost.Authorization.PrincipalPermissionMode = PrincipalPermissionMode.UseWindowsGroups;
serviceHost.Open();
That's it. We're now configured. Not so hard. Not really any more difficult than raw .NET remoting configuration. In fact, probably easier.
Of course, the Stop method on the ServiceRunner class executes the Close method on the service host.
Now to the implementation of the IMyWcfServiceControl interface in the MyWcfServer.cs file:
internal class MyWcfServer : IMyWcfServiceControl
{
[PrincipalPermission(SecurityAction.Demand, Role = @"nbdev2\allwcfgroup")]
[PrincipalPermission(SecurityAction.Demand, Role = @"nbdev2\mywcfgroup")]
public MyWcfServiceResponse ExecuteCommand(MyWcfServiceCommand command)
{
MyWcfServiceResponse response = null;
if (IsAuthorized())
{
response = new MyWcfServiceResponse()
{
Response = command.Command + " response " + DateTime.Now.ToLongTimeString()
};
}
else
{
response = new MyWcfServiceResponse()
{
Response = "Error: user not authorized"
};
}
return response;
}
bool IsAuthorized()
{
//The PrincipalPermission attributes are an OR proposition.
//The following enforces the AND requirement.
IPrincipal p = Thread.CurrentPrincipal;
if (p.IsInRole(@"nbdev2\allwcfgroup") && p.IsInRole(@"nbdev2\mywcfgroup"))
return true;
else
return false;
}
}
Note that we have two layers of security authorization employed here. (Authentication has already been handled automatically for us.) First we have two PrincipalPermission attributes that demand that the user be in one of two groups. But in fact, our invented requirement here is that the user belong to both groups. Don't ask me why. It's a demo.
So the IsAuthorized method is called and there we examine the Thread.CurrentPrincipal to learn if the requirement is met. This was a revelation to me. I did not know that the thread handling the single call to the WCF service would be running under the calling user context. A very good thing to know. Note to self...
MyWcfMon
Now to the client. It's simple really, and you could probably find an even more clever way to write it, but this might help you get started. You will find in the source a file called MyWcfServiceProxy.cs where the following code lives in the MyWcfServiceProxy class:
internal MyWcfServiceResponse ExecuteCommand(MyWcfServiceCommand command)
{
NetTcpBinding tcpBinding = new NetTcpBinding(SecurityMode.Transport, false);
tcpBinding.Security.Transport.ClientCredentialType = TcpClientCredentialType.Windows;
tcpBinding.Security.Transport.ProtectionLevel = ProtectionLevel.EncryptAndSign;
EndpointAddress endpointAddress =
new EndpointAddress(string.Format("net.tcp://{0}:{1}/MyWcfServiceControl", m_server, m_port));
MyWcfServiceControlClient proxy = new MyWcfServiceControlClient(tcpBinding, endpointAddress);
//Note: current user credentials are used unless we use runtime provided credentials like this
NetworkCredential credentials = new NetworkCredential(m_userName, m_password);
if (m_domain != null) credentials = new NetworkCredential(m_userName, m_password, m_domain);
proxy.ClientCredentials.Windows.ClientCredential = credentials;
MyWcfServiceResponse response = proxy.ExecuteCommand(command);
//if proxy is in Faulted state, Close throws CommunicationObjectFaultedException
if (proxy.State != CommunicationState.Faulted) proxy.Close();
return response;
}
class MyWcfServiceControlClient : ClientBase<IMyWcfServiceControl>, IMyWcfServiceControl
{
public MyWcfServiceControlClient(Binding binding, EndpointAddress address) : base(binding, address) { }
public MyWcfServiceResponse ExecuteCommand(MyWcfServiceCommand command)
{
MyWcfServiceResponse response = null;
try
{
response = Channel.ExecuteCommand(command);
}
catch (SecurityNegotiationException ne)
{
response = new MyWcfServiceResponse() { Response = ne.Message };
}
catch (SecurityAccessDeniedException ae)
{
response = new MyWcfServiceResponse() { Response = ae.Message };
}
return response;
}
}
The WCF client is easily configured programmatically as you can see. Create a Binding, an EndpointAddress, a NetworkCredential if you want to, and then call the service. The private MyWcfServiceControlClient class just makes it a little easier to isolate the configuration code in the code above it.
So now, we have a simple Form that does this:
string userName = txtUserName.Text;
string password = txtPassword.Text;
MyWcfServiceProxy sp = new MyWcfServiceProxy("localhost", 8239, userName, password, null);
MyWcfServiceCommand command = new MyWcfServiceCommand()
{
Command = "Hello world."
};
MyWcfServiceResponse response = sp.ExecuteCommand(command);
lblTest.Text = response.Response;
With this little framework, you can extend the command and response classes and build pretty much anything you want between your Windows Forms application and your Windows service with the comfort of secure, encrypted and signed communication between the two on the port of your choice.
If you find this useful, I'd love to hear about it. The code can be found here MyWcfService.zip (31.6KB) under the MIT license. Enjoy!