Consuming the Service
Any type of application can be a service consumer. Just as with old-fashioned web services, consumers need proxy classes to communicate with the services. Visual Studio makes it easy to create all this when you use the "Add Service Reference" option. You can use this feature to browse to a service and consume it from your client. Later, I'll go over everything I see wrong with this process, but I'll start by actually performing this task and examining what happens.
I created a Windows Forms application to consume the service I created earlier. When I right-click on my project and choose "Add Service Reference," I can browse to the service address (see Figure 1
) or simply drop-down the "Discover" button and select a service in my solution (where mine happens to reside). Because this technique creates proxy classes, service contracts, and data contracts in my client application, I need to choose a namespace in which to put them. For this example, I'll accept the default namespace of ServiceReference1
|Figure 1. "Add Service Reference" Dialog: Select a service address or click the Discover button to select a service in your solution.|
After adding the reference, Visual Studio creates several items in the client project, including a reference to System.ServiceModel and a new project tree section called "Service References" that contains one item called ServiceReference1
. This is the namespace name I provided when I added the reference.
Several additional files are not visible unless you turn on the "Show All Files" option in Solutions Explorer. A couple of levels deep in the file tree beneath the "Service References" you'll find a file called Reference.cs
. This file contains the three elements mentioned: a class that represents the data contract the referenced service uses, an interface representing the service contract, and a proxy class used to access the service's methods. Visual Studio places all these in a namespace composed of the project's name and the ServiceReference1
name that was the default in the Add Reference dialog. Incidentally, if you add a reference to another service, you cannot reuse the same namespace. The created proxy class name is the referenced service name followed by the word "Client," in this case, "Service1Client."
To access the service my client application simply needs to instantiate the Service1Client class and call its methods, which have the same names as the methods defined in the IService1 interface.
The configuration automatically created by adding a reference is quite extensive. In fact, Visual Studio creates a binding configuration that displays every available attribute, each set to its default values (see Listing 1). Again, this is nice when you want to learn about all the attributes, otherwise
well, I'll get back to that thought later.
Using the conventional techniques for creating services and consumers, you can hit the ground running rather quickly; but is this a good thing? Here's a brief analysis of what I find wrong with everything that you've just seen:
- Problem: Data contracts and service contracts should be in their own assembly.
Reason: When consumers are .NET, the contracts can be shared by them.
- Problem: Services should be in their own assembly.
Reason: Placing services in a separate assembly decouples them from the host.
- Problem: Service host should be a separate project.
Reason: Same as previous reason.
- Problem: Service host's web.config contains extraneous information.
Reason: Metadata endpoints won't be necessary for the techniques I'm planning to use.
- Problem: Client proxies should be in their own assembly.
Reason: More than one client application may want to use these services.
- Problem: Adding a service reference—bad idea.
Reason: Service and data contracts are duplicated, you have little control over the namespace name, and the process creates bloated configuration.
I'll revisit each of these and provide alternative approaches in the next section. While I've described the absence of certain project and item templates as a shortcoming, and I'll show you how to create everything manually, you can certainly create your own templates to compensate for those I feel are missing from Visual Studio is missing. There's a lot of documentation on the web on how to do this; among which is Matt Milner's MSDN Magazine article
The Manual Way
the Right Way
Having Visual Studio do everything for you may seem nice initially, but as you noticed above, it adds a significant amount of extra details to both my service and my consumer that are simply not needed—and in my opinion, not recommended. I recommend you do it all manually.
Now I'll talk about one of my biggest gripes, first and foremost. Accept the fact that when you design a service-oriented system, you're going to be dealing with many assemblies. There's nothing wrong with this in any type of design; so long as there are beneficial reasons for it.
When I design a WCF-based system, I'll create separate assemblies for the following components:
- Service and data contracts
- Business/ORM engines
- Service hosts
- Client proxies
- Client applications
After describing how to separate out the WCF-based components, I'll provide a step-by-step example by setting up an actual WCF-based application for both the service side and the client side.
The first piece of a service-oriented design I want to pull into its own assembly is the contracts. Though one of the tenets of SOA is that services should be able to service any type of client, .NET developers mostly deal with .NET on both sides. To anticipate this, I like to have both my service contracts and data contracts in their own assemblies. In the previous section I told you that Visual Studio created a copy of these on my consumer. Because I'll use a different technique to consume the service this time around, I'll need to create my own copies of the service and data contracts. And by placing them in their own assembly I'll be able to reuse this assembly in both my service and consumer.
Splitting up your contracts into their own assemblies does not mean you should put every contract in a different assembly, nor does it mean separating service contracts from data contracts. Use logical judgment to decide how the separation should happen. Unless I find a reason not to, I keep service contracts and their associated data contracts in the same project. The decision as to whether to put all the contracts in one assembly or have multiple assemblies should be based on some kind of logical contract grouping. The groupings may be determined by the services that will be using my contracts. For example, this project has Order and Product services in the design, and those are very specific to the business solution I am designing, so I'd group their contracts together in a single assembly. I'll follow that same grouping logic with the other services in the example.
One service in my system deals with obtaining geographical information. This service could certainly benefit other applications, so I chose to place the data and service contracts associated with it in its own assembly.
Another reason to consider splitting an assembly is security. Service contracts each have their own endpoint. One service can certainly implement multiple service contracts, thus having multiple endpoints. Perhaps one endpoint will be used for private access over the Intranet and another for public access over the Internet. I may chose to separate these contracts, which will make it easier to later create multiple proxy assemblies that different applications will use. One application may consume both the private and public contract, while another might require only the public one. Separating the assemblies ensures that projects don't have access to things they does not need or should not access. I'll go over the details of the contract implementation and service consumption later.
The next pieces of my design that I want in their own assemblies are the services themselves. I'll split these up into one or more assemblies based on the categorization I mentioned earlier. I might end up with one assembly for all my services, but chances are I'll have several, depending on how many services I have and their potential for inter-project reusability. Service projects will reference one or more of the contract assemblies I described previously. Also, depending on what these services do, there may be one or more business engines behind the scenes performing business logic or object-relational-mapping. The layers from the service level to the database are similar to a conventional business layer/data layer separation of concerns. Note that the service layer acts as a procedural entry point into what can be an elaborate object model, proving that OOP did not die because of SOA.
So far, this assembly separation provides the groundwork for some good reusability scenarios and promotes some good decoupling. My service projects reference only the contracts they need, and the services implement only those referenced contracts. The project contains nothing it doesn't need.
The service host should also be in its own assembly. Decoupling the services from the host lets you host your services in whatever type of host you want, and to change that host any time. Now, the host could be an IIS application, Windows Activation Services, or any self-hosting application including console applications, Windows Forms applications, Windows Services, etc. Decisions about the type of host and the bindings you want to use should not have any effect on the actual services themselves; a host should be able to reference service and contract assemblies and host them with little or no coupling. The only exception to my last statement would be in an MSMQ scenario, but that scenario's is outside the scope of this article. Hosts will find the service configuration information in either a web.config file or an app.config file, and that configuration should contain only what you need to expose your service the way you want and with the specifications you want. At a minimum, I'll need configuration information containing <service> entries with one endpoint per contract.
Now that my assembly separation is designed for my services, I can focus on the potential service clients, or consumers.
I won't use the "Add Service Reference" feature in Visual Studio. This is the largest culprit for anti-reusability and configuration bloat. Not only that, I don't even want to address my services directly from my clients. Service consumers use a proxy class to access a service's implementation. The proxy class is not difficult to write and, in fact, attacking this manually gives you a couple of different choices. I'm working here under the assumption that both sides of my application are .NET of course. If I had a Java client or some other type of .NET consumer, creating the proxy and parsing the service's message stream would be much different, and would use technology specific to that consumer.
A .NET service consumer not only needs a proxy class, but also service contracts that represent the messages that are being used. Now that I've decided to place my service and data contracts into their own assembly, I can now reuse that assembly by simply referencing it from my service consumer. The service consumer that contains the service proxies will also become its own assembly, giving me the ability to use the proxies from more than one client application. Remember, a client application does not necessarily have to be a UI application; it might equally well be an automatic process. Placing service client proxies in their own assembly is analogous to branching out the business layer of a conventional object-oriented architecture, making it accessible by a number of client tiers. Later, I'll go into more detail about how to create proxies and the choices available to you.
The last set of assemblies I need are the clients for my service proxies—the UI applications or unmanned processes that use the services.