WCF Clients Part 1 - Add Service Reference
The problem I am trying to solve...
Over the past couple of years I have written a number of WCF services, I have hosted them in IIS, in a windows service and even in console applications (OK that was only for test/demo purposes) but every time I have written the service I have also written a client to work with that service; more often the need for the service has arisen as a part of writing some other application that I am trying to write.
Let us assume that I am writing an ASP.NET MVC web site that needs to talk down to a database to retrieve information and this site is a simple site that allows a bank clerk to do very simple banking transactions but the first thing we need to focus on is delivering a system that has very basic account management:
- Create an account
- Find an account using its ID
- Find an account using the name of the customer and the account type
- Transfer money between accounts
- Deposit money to an account
- Withdraw money from an account
public interface AccountService
{
Account Get(int id);
Account Find(string customerName, AccountType accountType);
void Create(string customerName, AccountType accountType);
}
I would create the site but have the controller talk down to an internal service which would abstract away all of the business logic from the user interface, this service would be injected into the controllers to aid testability. Within the service I would expect a rich domain model and repositories as the data access layer. I hook everything up and it works; so far, so good.
However, as well as the ASP.NET site the client would like a group of super users to have a specific WPF application that has the same functionality but also has the ability to do:
- Delete a transaction
- Update account details
- Freeze an account
I now need to expose the original service that I created to two different clients and at this point I turn to WCF so I start by amending the interface definition:
[ServiceContract]
public interface AccountService
{
[OperationContract]
[FaultContract(typeof(AccountNotFoundFault))]
Account Get(int id);
[OperationContract]
[FaultContract(typeof(NoMatchingAccountsFault))]
Account Find(string customerName, AccountType accountType);
[OperationContract]
[FaultContract(typeof(InvalidCustomerNameFault))]
void Create(string customerName, AccountType accountType);
}
The next thing I need to do is to host this new service somewhere, assuming for now this is IIS I could just plug in the existing service implementation but I would need to change within the service itself what exceptions it is throwing. Personally, I would much prefer to keep the service implementation "as is" and write a wrapper for it that catches the specific exceptions and throws their equivalent faults.
OK, I've written the wrapper service, hosted it and I'm ready to go... well apart from the fact that I haven't yet done anything to the ASP.NET MVC or WPF clients.
Add Service Reference
On my client projects, which normally exist within the same solution as the service (as the service was formed from it) I now need to use the "Add Service Reference" wizard. I locate my service and add the reference, luckily, if I have my service contract (i.e. the interface, faults and models) located in their own assembly I am happy to use the option to re-use the models that I am referencing, however it generates a new interface for me.
I lose my reference to the original interface...
I am forced to change everywhere that referenced my original service interface to point to the client interface that has been generated despite the fact that the original interface is still perfectly usable. For me this is a problem, especially for my development, if I want to check some functionality out in a hurry I want the ability to just run the system and not care whether I have configured the WCF endpoint correctly on the server or the client; I just want to run the the service in-line to test the logic. Whether we use a WCF service or an in-line service on the production environment does not matter to me when I am tracking down a bug within the business logic.
You can edit the automatically generated reference to point back at your original interface (solve's this problem doesn't it) but that is extremely hacky and requires someone to know that every time you re-generate the reference you have to do this work and that is storing up problems for whoever is following you to maintain this code.
I now have to change all of my exception handling...
There is now also the problem with the exceptions that the service was originally throwing because all of my catch blocks need to be changed to catch the fault exceptions. They are no longer catching the original exceptions which means that it becomes even more difficult just to replace the WCF service at runtime with an in-line version. There is now a dependency, the client knows that it is talking via WCF and should the client actually care? Should any logic within the client care about how it is communicating with the service?
Again, the work-around is to catch both the original exceptions and the fault exceptions but that is really starting to make your code ugly and any logic is now still very dependent on knowing that we are talking over WCF.
The client is now disposable...
When we talk over WCF a channel is opened to the service by the automatically generated client and this resource needs to be released which is why the generated clients are disposable. All calls now need to be wrapped within a using statement so that we can correctly dispose of the resource.
But what happens if there is an error? Well, on error dispose is not the method that you should call and a lot of people do not notice this. When an exception is thrown by the service then it is the client's job to call
Abort() on the service to correctly dispose of any resource.
All of this is causing the client code to change significantly for no actual benefit, in my opinion all of this should be handled internally within the generated classes.
So what next?
I have now finished ranting about the problems of using "Add service reference" and there are people out there who will tell me that using
ChannelFactory<T> would solve some of my issues above and to those people I would say that you are partially correct but
ChannelFactory<T> is the subject of my next post.
Once I have finished explaining the problems of
ChannelFactory<T> I will follow that with a post showing my solution to this problem as I release an open source WCF client generator to handle the issues above, so stay tuned...
Tags: