I've been wanting a simplified WCF plugin architecture for .NET Windows service with easy debug console mode to make my life easier. Building services with Windows Communication Foundation (WCF) is simple and hard. The service and data contract code is simple, like this:
namespace Test2Common
{
[ServiceContract]
public interface IMyOtherService
{
[OperationContract, FaultContract(typeof(WcfServiceFault))]
string GetName(string seed);
[OperationContract, FaultContract(typeof(WcfServiceFault))]
Person GetPerson(int id);
}
[DataContract]
public class Person
{
[DataMember]
public string Name { get; set; }
[DataMember]
public string Title { get; set; }
}
}
But that is where simple ends. The hosting and client proxy code and the configuration XML (or code) is not simple. Even when I use tools to generate these artifacts, I find myself having to tweak and fix and delve deeper than time permits. And since I prefer simple, I decided to create a reusable framework for hosting my limited scope services. Here's what I wanted (download PluggableWcfServiceHost.zip (38.05 KB)):
- Fast net.tcp binding
- Windows credentials and authentication
- Encrypted and signed transport (no packet sniffing allowed)
- Simplified configuration (hide everything I don't want to see)
- Windows Service host that behaves like a Console app when I'm debugging
- Dynamic loading of the service (no changes to the host code to add a new service)
- Generic client so I don't have to write or generate proxy code
- Client that is truly IDisposable (hide Abort vs Close for me)
- Long timeout in DEBUG mode so I can really take my time while debugging
- Inclusion of exception details in DEBUG mode only
- Base service class with a simple Authorize method to support multiple Windows groups
- Support for multiple Windows group authorization
- Identical configuration for server and client
- Cached resolution of service plugin service and contract types
- Minimal number of assemblies (projects) in the solution
- Keep the implementation of the service hidden from the client
- (Probably some I've forgotten to mention)
Here's the simplified config with two services configured:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="wcfServices"
type="WcfServiceCommon.WcfServiceConfigurationSection, WcfServiceCommon" />
</configSections>
<appSettings/>
<connectionStrings/>
<wcfServices consoleMode="On">
<services>
<add key="test1"
serviceAddressPort="localhost:2981"
endpointName="Test1EndPoint"
authorizedGroups="WcfServiceClients,someOtherGoup"
hostType="Test1Service.ThatOneService, Test1Service"
contractType="Test1Common.IThatOneService, Test1Common" />
<add key="test2"
serviceAddressPort="localhost:2981"
endpointName="Test2EndPoint"
authorizedGroups="WcfServiceClients,someOtherGoup"
hostType="Test2Service.MyOtherService, Test2Service"
contractType="Test2Common.IMyOtherService, Test2Common" />
</services>
</wcfServices>
</configuration>
And here's the implementation of the service (It really is simple):
namespace Test2Service
{
public class MyOtherService : WcfServiceBase, IMyOtherService
{
public string GetName(string seed)
{
base.Authorize();
return "This is my name: " + seed.ToUpper();
}
public Person GetPerson(int id)
{
base.Authorize();
return new Person { Name = "Donald Trumpet", Title = "King of the Hill" };
}
}
}
Now for the really fun part. The client. Hey, where's the proxy? Hidden away just like it ought to be.
namespace WcfSvcTest
{
class Program
{
static void Main(string[] args)
{
using (var client1 = WcfServiceClient<IThatOneService>.Create("test1"))
{
Console.WriteLine(client1.Instance.GetName("seed"));
var add = client1.Instance.GetAddress(8);
Console.WriteLine("{0}", add.City);
}
using (var client2 = WcfServiceClient<IMyOtherService>.Create("test2"))
{
try
{
Console.WriteLine(client2.Instance.GetName("newseed"));
var per = client2.Instance.GetPerson(7);
Console.WriteLine("{0}, {1}", per.Name, per.Title);
}
catch (FaultException<WcfServiceFault> fault)
{
Console.WriteLine(fault.ToString());
}
catch (Exception e) //handles exceptions not in wcf communication
{
Console.WriteLine(e.ToString());
}
}
Console.ReadLine();
}
}
}
To save space, I only wrapped the use of the client in try catch on the second test service.
Note: You have to remember that the WcfServiceHost requires the "common" and the "service" assemblies of your dynamically loaded services in it's bin folder. The client (see WcfSvcTest project in the solution) will also need a copy of the "common" assemblies in it's bin folder. You'll find I'm doing that for the test using post-build commands (copy $(TargetPath) $(SolutionDir)WcfServiceHost\bin\debug\). And of course, both need to have identical config sections as shown in the code.
I hope you find this WCF plugin architecture and the accompanying code useful. I will certainly be using it. If you do use it, please let me know how it goes.
Download PluggableWcfServiceHost.zip (38.05 KB)