Wednesday, 2 April 2008

WCF: Hosting multiple WCF services as one single service

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.

35 comments:

Anonymous said...

I'm working on something similar to what you've presented here, only I need to send out the configuration and class file for the endpoints individually. Do you know if there is a way to do that using svcutil or another method, besides manually editing the config and class file?

Thanks!

Dan Meineck said...

@Anonymous:

I'm not sure what you mean by 'sending out the configuration and class file for the endpoints individually'. Can you elaborate?

Do you mean that in the same project you want to host different services with separate config files for each one? I don't think this is possible.

Anonymous said...

Looks like your SharePoint connectivity expertise is moving on in leaps and bounds. I'll send this on to the guys at EPiServer ;)

Anonymous said...

Good way of organizing. Would require the method names in all of the interfaces to be unique.

rull_orion said...

Thank you, that is something that I was looking for.

sunny said...

thats great but how will sessions work in your case. what i mean to say is that you will create proxy for a particular endpoint and when you call proxy.close() session will terminate! is there a way to have a sigle proxy for all the endpoints (so that there is one session!).
please reply
thanks
sunny
s26f84 (at) gmail

Anonymous said...

This will work, but your Service class contains all the functionality.

You may want to break the three pieces into separate assemblies and use Port Sharing (see the MSDN and http://www.codeproject.com/KB/WCF/Port_Sharing__in_WCF.aspx for details)

Anonymous said...

what happened to the .svc file in your wcf project? i can't seem to add service reference to my WCF service application because service discovery can't find it. Please help!

Dan Meineck said...

@Anonymous:

In this example I wasn't using an SVC as I was self hosting my WCF service under a NT Service. I was able to add a reference to my WCF service in visual studio by simple specifying the MEX address.

Does your SVC file exist in your WCF configured website? Can you browse to it using a web browser?

E-mail me if you need any more help and I'll see what I can do.

Dan

Anonymous said...

I can't seem to get the netTcpBinding working on my Windows XP Pro SP2 box. I tried adding a service reference but it gave me an error: "Could not find a base address that matches scheme net.tcp for the endpoint with binding NetTcpBinding. Registered base address schemes are [http]"

Jon said...

What about the SVC file of the service, the markup value of the CodeBehind? eg. [ServiceHost Language="C#" Debug="true" Service="Service" CodeBehind="????"]

What will be the value of it?

Thanks in advance.

Dan Meineck said...

Hi Jon,

I wasn't using an SVC, in my implementation the service was hosted using a Windows Service that started a ServiceHost object that I created. The ServiceHost object in it's constructor took typeof(Service), where 'Service' was the partial class that was implemented.

As a result, I believe in the Service attribute of the SVC you would have the full namespace qualified classname of the partial class for the Service, and there would be no codebehind. Instead of codebehind, you should have the entire service in a seperate assembly DLL that is referenced in the website web.config.

Does that make sense?

ppunia said...

Hi,

i have done the same thing that you suggest in your blog. because same i had to do earlier. but now i am having one issue while hosting and consuming my service.

i have created a proxy class and config. file for the implementation of the service at consumer end using svcutil.exe.

CONFIG :
--system.serviceModel--
--bindings--
--basicHttpBinding--
--binding name="BasicHttpBinding_IVideoService" closeTimeout="00:01:00"
openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00"
allowCookies="false" bypassProxyOnLocal="false" hostNameComparisonMode="StrongWildcard"
maxBufferSize="65536" maxBufferPoolSize="524288" maxReceivedMessageSize="65536"
messageEncoding="Text" textEncoding="utf-8" transferMode="Buffered"
useDefaultWebProxy="true"--
--readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
maxBytesPerRead="4096" maxNameTableCharCount="16384" /--
--security mode="None"--
--transport clientCredentialType="None" proxyCredentialType="None"
realm="" /--
--message clientCredentialType="UserName" algorithmSuite="Default" /-
--/security-- --/binding--
--binding name="BasicHttpBinding_IPhotoService" closeTimeout="00:01:00"
openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00"
allowCookies="false" bypassProxyOnLocal="false" hostNameComparisonMode="StrongWildcard"
maxBufferSize="65536" maxBufferPoolSize="524288" maxReceivedMessageSize="65536"
messageEncoding="Text" textEncoding="utf-8" transferMode="Buffered"
useDefaultWebProxy="true"--
--readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
maxBytesPerRead="4096" maxNameTableCharCount="16384" /--
--security mode="None"--
--transport clientCredentialType="None" proxyCredentialType="None"
realm="" /--
--message clientCredentialType="UserName" algorithmSuite="Default" /--
--/security--
--/binding--
--binding name="BasicHttpBinding_IShareYourMind" closeTimeout="00:01:00"
openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00"
allowCookies="false" bypassProxyOnLocal="false" hostNameComparisonMode="StrongWildcard"
maxBufferSize="65536" maxBufferPoolSize="524288" maxReceivedMessageSize="65536"
messageEncoding="Text" textEncoding="utf-8" transferMode="Buffered"
useDefaultWebProxy="true"--
--readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
maxBytesPerRead="4096" maxNameTableCharCount="16384" /--
--security mode="None"--
--transport clientCredentialType="None" proxyCredentialType="None"
realm="" /--
--message clientCredentialType="UserName" algorithmSuite="Default" /--
--/security--
--/binding--
--/basicHttpBinding--
--/bindings--
--client--
--endpoint address="http://localhost:1111/SocialLibrary/soap" binding="basicHttpBinding"
bindingConfiguration="BasicHttpBinding_IVideoService" contract="IVideoService"
name="BasicHttpBinding_IVideoService" /--
--endpoint address="http://localhost:1111/SocialLibrary/soap" binding="basicHttpBinding"
bindingConfiguration="BasicHttpBinding_IPhotoService" contract="IPhotoService"
name="BasicHttpBinding_IPhotoService" /--
--endpoint address="http://localhost:1111/SocialLibrary/soap" binding="basicHttpBinding"
bindingConfiguration="BasicHttpBinding_IShareYourMind" contract="IShareYourMind"
name="BasicHttpBinding_IShareYourMind" /--
--/client--
--/system.serviceModel--



but when i am calling the function of my services then i got the following error:

Could not connect to http://localhost:1111/SocialLibrary/soap. TCP error code 10061: No connection could be made because the target machine actively refused it

Alan said...

Really usefull post Dan, helped explain something which I cuoldn't get otherwise. I am making an Expression blend desktop application that is connecting to a wcf service set up in this way. Before I separated the services into different endpoints I authenticated against the service (using forms authentication and cookies) when the application started and was then authenticated for every service request from then on. Since splitting them into different endpoints I have to authenticate against every endpoint I connect to the first time I do so.

Is this expected behaviour or am I doing something wrong? Ideally, once I authenticate on any endpoint on the service, I want the authentication to be persistant across all endpoints in that service - can this be done?

Thanks,

Al.

Anonymous said...

Very interesting post. Im have kind of the same architecture. Does anybody know in which way i can arrange the services so they can call eachother to use their functionality?

Regards
Nick

Anonymous said...

Very Useful article.
Thank u

星願 said...

Hi,

May I have your full source code for example? I also create a wcf service console app, but it seems some problems.

hema said...

Can you please post the WCF service and client code. It will really help to evarbody if we get sample code.

Thanks,
- hema

Miguel Madero said...

I found a nice alternative to do it with configuration here:
http://www.webhost4life.com/HostingKB/KnowledgebaseArticle50088.aspx

Anonymous said...

Previous link posted by Miguel Madero, does not seems to be valid anymore.

I would have been interested in this information, could you please update with the valid link .

Thanks

Seb

Anonymous said...

Thanks, helped a lot!

Anonymous said...

where can I find the code of this?

Anonymous said...

How do I specify multiple authentications in one binding configuration. Something like:

Anonymous said...

Nice post but I am having one problem.
I am unable to add reference to such a service with multiple contract and one class in a silverlight 4 application. The error is "Custom tool error: Failed to generate code for the service reference 'ServiceReference1'. Please check other error"

Anonymous said...

Hi Dan,

Trying to find a solution for using Multiple Services, I came across Your blog. I also had to split an existing service into two. I also tried the way You describe here, but all to no avail. I must admit, we do have Spring.Net included on Service side, which makes things allways different from the book.

However, nothing worked so I finally created a new project for the second service and had it all running nicely, only my second service was not properly springyfied. So I just copied both the .svc and .svc.cs from second service project into our first service, added a new endpoint, updated Spring.configuration and there You have it. Two Services in one.

greets Carsten

ykapase said...
This comment has been removed by the author.
ykapase said...

Very nice article.

Michael Freidgeim said...

Would be better to have small wrapper class, implementing all 3 interfaces and redirecting the actual work to 3 separate classes?

Dan Meineck said...

@Michael - that would work - but wouldn't be great for a larger solution as more service interfaces got added - especially when you factor in multiple developers working on it in a source controlled setup - separate files are the way to go in that scenario.

Rayman said...

Great..exactly what i was also looking for..keep up the good work

Anonymous said...

Is there a way on the client side to have all the methods under one ServiceClient?

Anonymous said...

On my own I came to the same solution but can't get it to work. I have two service contracts implemented in a single service but on the client side SVCUTIL generates TWO separate service client classes and when I try to access the 2nd I get an error indicating it couldn't find such endpoint.

Roel de Brouwer said...

Code post without formatting. Brrrrrrr

Anonymous said...

I know, it´s some time ago, but how to start the listener? I always get Errors (endpoint not found)

Can you post the sources for a sample?

Thanks

Krishna Kishore Devarakonda said...

I have 14 interface service contracts and common methods in all 14 of them. So, I decided to have a base interface from which all the 14 interfaces extend. In addition I implemented the base interface with all the method implementations as per the base contract. Now, I wish to have to have a single service URL with multiple end points instead of 14 different URLs. Can I achieve this scenario using Partial classes? If so, how?