On Monday I needed to split up a
WCF service I had written for SharePoint into multiple services, one for Document Libraries, one for Lists, and another 'Common' service which has some standard methods like
Ping() and
GetVersion().
It didn't make sense to me to have all this functionality for lists, document libraries and common stuff all in one service, yet I didn't want to have to host 3 separate services on different WCF endpoints and port numbers.
I couldn't find anything about this on the web or in the excellent WCF book
"Learning WCF" by Michele Leroux Bustamante. Luckily me and my colleague
Dave Cromar worked out how to achieve this!
We have our 3 seperate Interface contracts:
[ServiceContract(Name = "Utility", Namespace = Constants.COMMON_SERVICE_NAMESPACE)]
public interface IService
[ServiceContract(Name="Documents", Namespace = Constants.DOCUMENTS_SERVICE_NAMESPACE)]
public interface IDocumentService
[ServiceContract(Name = "Lists", Namespace = Constants.LISTS_SERVICE_NAMESPACE)]
public interface IListService...each one has the various methods defined in the interface and decorated with the WCF attributes such as
OperationContract and
FaultContract, as per normal.
Now, in our concrete implementations of these interfaces, instead of having 3 seperate classes, we have one class, but defined as a partial class, split up into 3 seperate CS files, each class definition implementing it's corresponding interface,
IService,
IDocumentService and
IListService.
So it looks like this:
Service.cs:
[ServiceBehavior(Name="SharePoint")]
public partial class Service : IService
{
#region IService Members
// Common Service, ping, getVersion etc.
// Implementation of Service class goes here
...
#endregion
}DocumentService.cs:
public partial class Service : IDocumentService
{
#region IDocumentService Members
// Implementation of DocumentService interface goes here
...
#endregion
}ListService.cs:
public partial class Service : IListService
{
#region IListService Members
// Implementation of ListService interface goes here
...
#endregion
}This all compiles nicely - note that the 3 class definitions have to be in the same namespace because they are all the same class (partial).
One last thing - the WCF app.config configuration:
Basically, the configuration is exactly as per a normal configuration with a single service node defined, but with three separate endpoints:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<!-- When deploying the service library project, the content of the config file must be added to the host's
app.config file. System.Configuration does not support config files for libraries. -->
<system.serviceModel>
<services>
<service name="MyNamespace.Service"
behaviorConfiguration="MyNamespace.ServiceBehavior">
<!-- Service Endpoints -->
<!-- Unless fully qualified, address is relative to base address supplied above -->
<endpoint address="Common"
binding="netTcpBinding"
bindingConfiguration="netTcpStreaming"
contract="MyNamespace.Common.Contract.IService" />
<endpoint address="Documents"
binding="netTcpBinding"
bindingConfiguration="netTcpStreaming"
contract="MyNamespace.Documents.Contract.IDocumentService" />
<endpoint address="Lists"
binding="netTcpBinding"
bindingConfiguration="netTcpStreaming"
contract="MyNamespace.Lists.Contract.IListService" />
<!-- Metadata Endpoints -->
<!-- The Metadata Exchange endpoint is used by the service to describe itself to clients. -->
<!-- This endpoint does not use a secure binding and should be secured or removed before deployment -->
<endpoint contract="IMetadataExchange" binding="mexHttpBinding" address="mex" />
<host>
<baseAddresses>
<add baseAddress="http://localhost:8000" />
<add baseAddress="net.tcp://localhost:9000" />
</baseAddresses>
</host>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="MyNamespace.ServiceBehavior">
<!-- To avoid disclosing metadata information,
set the value below to false and remove the metadata endpoint above before deployment -->
<serviceMetadata httpGetEnabled="True" />
<!-- To receive exception details in faults for debugging purposes,
set the value below to true. Set to false before deployment
to avoid disclosing exception information -->
<serviceDebug includeExceptionDetailInFaults="false" />
</behavior>
</serviceBehaviors>
</behaviors>
<bindings>
<netTcpBinding>
<!-- Max file size 4GB, max message size 64KB, timeout 1 hour -->
<binding name="netTcpStreaming" transferMode="Streamed" maxReceivedMessageSize="4294967296" maxBufferSize="65536" sendTimeout="01:00:00" />
</netTcpBinding>
</bindings>
</system.serviceModel>
</configuration>Notice the three endpoints defined with the different address values - these get appended onto the base address defined, so that when a client discovers the service, the app.config created will have three seperate bindings (one for each service interface), and three seperate endpoints. Each endpoint will have different addresses, but crucially, the same base address:
<endpoint address="net.tcp://localhost:9000/Common" binding="netTcpBinding"
bindingConfiguration="NetTcpBinding_Utility" contract="Service.Utility"
name="NetTcpBinding_Utility">
<identity>
<servicePrincipalName value="host/localhost.domain" />
</identity>
</endpoint>
<endpoint address="net.tcp://localhost:9000/Documents"
binding="netTcpBinding" bindingConfiguration="NetTcpBinding_Documents"
contract="Service.Documents" name="NetTcpBinding_Documents">
<identity>
<servicePrincipalName value="host/localhost.domain" />
</identity>
</endpoint>
<endpoint address="net.tcp://localhost:9000/Lists" binding="netTcpBinding"
bindingConfiguration="NetTcpBinding_Lists" contract="Service.Lists"
name="NetTcpBinding_Lists">
<identity>
<servicePrincipalName value="host/localhost.domain" />
</identity>
</endpoint>This allows us to split out our service into seperate logical entities, but still expose it in one single place - job done!
Any questions - please use the comments.