Applying the Concepts
To illustrate all these concepts, I'll build a simple system with all the aforementioned components. Figure 2
and Figure 3
illustrate the application architecture and assembly separation in the examples.
|Author's Note: Although the diagrams illustrate a complete system model with both Product and Order services, for the purposes of this article, I will concentrate only on the Product part of the system, and will not show any error handling or fault contract code in the examples. I take those as a given when developing real applications.
The Service Side
|Figure 2. Application Architecture: The figure illustrates the architectural layers of the application.||
|Figure 3. Assembly Separation: Data contracts, service contracts, services, and client proxies should reside in separate assemblies.||
I'll code the service side first, so you can see the detailed separation of the components.
I'll start at the bottom of the stack and design my data contracts first. Because I'm going to concentrate only on the Product side of the system, I need one data contract called "ProductData." This contract will contain product information data. For the rest of this article, I'll keep all my examples as simple as possible. I'll place my data contract class into an assembly I'll call "Contracts," which will later contain the service contract as well. The ProductData class will use the DataContract
attributes that come from the required reference to the System.Runtime.Serialization assembly. You can see this class in Listing 2
The Contracts project also contains two product service contracts, IProductBrowser and IProductAdmin. There's no need to place these into separate assemblies.
IProductBrowser contains operations that allow browsing a product catalog. It defines operations that support the retrieval of a single product or an entire product listing. It will also contain operations that search for products, given some incoming search specifications.
IProductAdmin contains operations for adding and maintaining the product catalog. These two contracts are separate because later you'll see that service endpoints are defined on a per-contract basis. Separate contracts let me control access to these two sets of operations individually. Listing 3
shows the full code for these two service contracts. The ServiceContract
attributes used in these interfaces are defined in the System.ServiceModel namespace, another assembly I need to reference: System.ServiceModel.dll
. Also notice that the ServiceContract
attribute defines the name and unique namespace for my service contracts. True to the polymorphic nature of interfaces, these service contracts merely define the operations, which is all that clients really need to know about (as the proxies will demonstrate later). The next project contains the implementation details and will remain behind the services layer.
Now it's time to create the services. Besides the benefit of reusing my contracts from my client proxies later, separating them from this "services" project also allow me to create multiple implementations of each contract if I so desire.
This project will be called "Service" and references not only System.ServiceModel but my Contracts assembly as well. The project contains one service class called "ProductService." Here's where it gets interesting. This service will provide implementations for both the IProductBrowser and IProductAdmin contracts, but this in no way means that a consuming client must be exposed to both implementations, as you'll see later. The implementation in this service will be the meat of my service and can call out to anything it needs to perform its functionality. In the interest of time and printed space I won't provide a detailed implementation, so you'll have to settle for commented pseudo-code. Just remember that this is the code layer that can call out to either another service and/or one or more business engines or ORM layers to perform its tasks and return information to the calling client. Listing 4
shows the code for the ProductService class.
Here's a recap of the process so far. I have my contracts (both data and service) in one assembly, which is referenced by another project containing my service. I'm in good shape so far for contracts and I've also left myself open for any hosting scenario I want. I've chosen to create two hosting applications, one running under IIS and another that's a console app, demonstrating self-hosting.
I chose the two hosting scenarios so I can expose a public service using IIS in HTTP and a private service using TCP being hosted in the console application. Remember earlier I said that endpoints are created on a per-contract basis. You also saw that I split off my public and admin functions into two separate service contracts. Given this information, you can conclude that each service contract will need to be exposed using its own endpoint and can thus be exposed using its own host.
In this case, I want to host the IProductBrowser contract using IIS and HTTP so I don't have to deal with any firewall issues and can deploy as seamlessly as possible, allowing access by clients of any type, not just .NET WCF ones. In contrast, the IProductAdmin contract will be exposed using a TCP host running in a console application, because it will sit inside a firewall. This service will benefit from the speed of TCP and will be consumed only by .NET clients.
I could have certainly chosen to host both service contracts using IIS, but I would be limited to HTTP transport on a service that would not be public and can sit inside a firewall. You can probably already see the benefits of decoupling the hosts from the rest of the components.
With that in mind, I've created two more projects in my solution: a console app and an ASP.NET application. Both projects must reference the System.ServiceModel assembly and the Service and Contracts assemblies. Notice that I didn't
create a WCF Service Application to serve as my host; I used a standard ASP.NET application and simply deleted the Default.aspx
page, added my references, and cleaned up the web.config
file a bit. In this project, I'll need a SVC file to serve as my service's browsing point—but I don't want to create one using the "Add New Item" feature. That will bloat my web.config
file and I just finished cleaning that thing up! Instead I added a text file item to my application and renamed it ProductService.svc
. Unlike the "everything automatic" walkthrough I gave you at the beginning of this article, this SVC file will not have a code-behind file containing the service implementation (remember, I already have that in the referenced Service project). Instead, I'll point this SVC file to the ProductService class by manually creating the directive:
It's important to note that the address of the service hosted here is the URL corresponding to the deployment location of this web application, followed by ProductService.svc
, but more about that in a minute.
Now let's switch to the console host. My console application is called Host
—and like all good console applications, starts with a class called 'Program" and a static method called Main
. Self-hosting a service simply means instantiating the System.ServiceModel.ServiceHost class, giving it information about the service, and opening the host. The rest is all in the configuration (something my IIS host will need as well). Here's the console host code:
static void Main(string args)
ServiceHost serviceHost =
"Service running. Please 'Enter'
Notice that the code for self-hosting is actually quite simple. At minimum, the ServiceHost class needs the type of service it will be hosting. Then all you have to do is call its Open method to start the host. At this point, the host is "listening" for service requests from consumers. The "console" code that follows is there simply so that the console application displays something
on the screen. Then it waits until a user presses Enter before closing.
So now both my service hosts know what service they will be servicing—but not what contract and binding they will expose and use. So now it's time to talk about configuration. If I had created these hosts using the project templates, I would have been given a lot of unneeded configuration information. I much prefer to create it by starting with the bare minimum, and then adding what I need as I feel I need it.
The bare minimum a service needs in order to be hosted is the ABCs of WCF: an Address
, a Binding
, and a Contract
. These three things make up a service's endpoint. You define them in the <services>
section inside the <system.serviceModel>
section of the configuration file. The two hosting applications both host the same service, but each exposes a different service contract through a different binding. The ASP.NET hosting application's configuration file contains this configuration:
The console application's configuration file uses this configuration:
Notice that the ASP.NET app's configuration does not have an address. Again, that's because the service address is the deployment URL of the ASP.NET application followed by the name of the SVC file. The console app, on the other hand, does have an address in the configuration.
Also note the bindings each endpoint uses. The last part is the contract that each endpoint exposes. As planned, the public HTTP service host exposes the IProductBrowser contract and its operations to either .NET or non-.NET consumers; while the more restrictive TCP service host exposes the IProductAdmin contract specifically to .NET consumers.