WCF services and shared assemblies
April 6, 2011 4 Comments
I often see situations on projects where they have design challenges with multiple webservices who share the same datacontracts (DTO). I already wrote a post about the separation of the service contract and the DTO’s, but i would like to extend that post with my vision on how we should work with mutiple services build on top of the same DTO’s.
To illustrate this I have build two WCF services which both have a reference to the DTO project called MyDataContracts. The solution for my services looks like this:
Project 1 & 3 are service contracts and service implementations and project 2 contains the shared DTO’s.
The service contracts are very simple
[ServiceContract] public interface ICustomerService { [OperationContract] Customer GetCustomer(int value); }
[ServiceContract] public interface IInvoiceService { [OperationContract] Invoice GetInvoice(int invoiceId); }
The invoice DTO is implemented below
[DataContract] public class Invoice { int invoiceId = 0; Customer client; [DataMember] public int InvoiceId { get { return invoiceId; } set { invoiceId = value; } } [DataMember] public Customer Customer { get { return client; } set { client = value; } } }
Because the invoice DTO has a datamember of the type customer the clients who want to interact with the invoice service have to know how a customer DTO will look like.
So that’s it for the service side. Now we take a look at the client side. When I consume services I usually create a service agent project which will be responsible for handling the calls to the services. This way I only have to implement exception handling once for the communication with my services. So my service agent project has two service references (two proxies). These two proxies will receive there own version of the customer DTO when I generate the proxy in the standard way. As you can see in the object browser below, both proxies have a Customer DTO generated. This can give you some annoying issues especially when you have to map values to your customer DTO and you have to pick a different one depending on the service you are communicating with.
My client solution looks like this
The first project is just a console application which interacts with my second project the service agent. I have created a service agent class.
using System; using System.Collections.Generic; using System.Linq; using System.Text; using MyServiceAgents.CustomerProxy; using MyServiceAgents.InvoiceProxy; using System.ServiceModel; namespace MyServiceAgents { public class ServiceAgent { public Customer GetCustomer() { CustomerServiceClient proxy = new CustomerServiceClient(); try { return proxy.GetCustomer(1); } catch (FaultException ex) { // catch any faultexception from the service } catch (CommunicationException cex) { // ... } } public Invoice GetInvoice() { InvoiceServiceClient proxy = new InvoiceServiceClient(); try { return proxy.GetInvoice(1); } catch (FaultException ex) { // catch any faultexception from the service throw new Exception(); } catch (CommunicationException cex) { // ... } } } }
The service agent has two methods. Each method is responsible to communicate with one of the two services. To make sure my proxy classes are available i have to add the following using statements
-
using MyServiceAgent.CustomerProxy
-
using MyServiceAgent.InvoiceProxy
- Adding these using statements will give us the problem of ambiguous classes.
To resolve this issue you could use a shared assembly for the DTO’s. You have to make sure that when the service reference is added on the client project it doesn’t generate any DTO’s anymore. To make this possible you have to share your DTO assembly with your consumers, which looks fine to me. This will give you the following situation.
I hope that sharing an assembly doesn’t need any explanation. To make sure that adding a service reference doesn’t generate any DTO’s you only have to add a reference from your service agent project to the DTO assembly. This way Visual Studio knows how to handle the generation of your service reference. To be sure click on the advanced button when you add a service reference.
Make sure the checkbox ‘Reuse types in referenced assemblies’ is enabled and you are good.
This solution can be related the the following SOA pattern: Canonical schema The DTO’s are set as standard across service contracts within an inventory boundary.
Regards,
Dennis
Pingback: WCF Services And Shared Assemblies? Dennis Van De Laar | Microsoft WCF Knowledge Sharing
So in this example where would your domain Invoice class be located? Would it exist as two separate classes in both the client and server assemblies? Not in the shared assembly where the service contract resides? If so, it seems there may be some similar logic on both the client and server versions that might get duplicated. If not you may be exposing service specific details to the client(s).
Likewise how might the mapping be different / similar / shared on both sides? Would you use something like AutoMapper to convert between DTO and DO within/after each service method?
I’m liking what you are saying but a sample solution and/or some more specifics might help with the full picture.
Hi Geoff,
Thanks for your feedback. I will provide a working solution as soon as possible.The domain invoice will only be on the serverside. You should map your domain invoice to you DTO invoice. The DTO invoice will be in the shared assembly. This assembly will be referenced at client and serverside. You can map your DTO’s on the client side to UI controls or other UI classes.
So the step taking from the service to the client will be:
Domain invoice –> Automapper –> DTO invoice –> Send over the wire –> DTO incoive –> UI control (Invoice control).
The only assembly shared will be the DTO assembly. The domain object will stay on the serverside so there will be no details shared with the clients. This way you are also able to modify your service implementation without updating your client’s proxy. I hope i was able to answer your questions. I will update my post to clarify certain parts.
Thanks you for the post and the clarification. That makes sense. In my case on the client side the immediate consumer of the class isn’t a UI control. I can see some need for some common functionality useful on both the client and the server, while some data and functionality is only applicable at one or the other.
For example the raw data in the DTO’s may have all the data that is needed but there might be calculated properties, readonly convenience properties, helper methods, calculations, and maybe even light validation that might be useful on both the client and the server. If that were put into the shared classes then they are no longer just DTO’s and it is the uglier shortcut you were referring to.
If not there I was just thinking through how to best structure things if there is a need for both shared and different functionality on the client and server…