Articles in this series
When you get started with WCF, there a few basic things that
you need to know to design, implement and host your services. In fact the WCF
templates for Visual Studio make it a single step process in some cases since
they start you off with a sample service that can be immediately hosted and
tested with the WCF testing tools. Calling a service from a client application
is also quite simple – just generate a proxy and write code against the object
model.
Once you get past your first sample, however, there are a large
number of design choices and features to consider for your services, hosting
environment and clients. This chapter of WCF Essentials will serve as a summary
of the steps you will take to design, implement, host and consume WCF services.
The purpose is not to explain the details behind each design choice or feature
– but to make you aware that these are things you should consider. Later
sections of this whitepaper will provide recommendations to narrow the choices
you face.
Designing a Service Contract
To implement a WCF service you start by designing a service
contract, and then implementing that contract on a service type. A simple
service contract usually involves applying the ServiceContractAttribute to an interface and then applying the OperationContractAttribute to each
method to expose as part of the service contract. Example 1
illustrates a simple WCF service contract with a single service operation.
NOTE: I have supplied a
namespace for the service contract instead of relying on the default namespace.
This is a best practice that will be discussed in a later chapter.
Example 1: A simple
WCF service contract
[ServiceContract(Namespace="urn:WCFEssentials/Samples/2008/12")]
public interface ISimpleService
{
[OperationContract]
string SendMessage(string message);
}
You should at least consider the following when designing a
service contract:
- What properties should be set on the and ?
- Should other attributes be applied to the
service contract or its methods – for example those related to fault contracts,
sessions, transactions and web programming (non-SOAP messaging)?
- What complex types will be used for
serialization? Should those types be defined as inferred data contracts, data
contracts, serializable, or IXmlSerializable? Are there any custom
serialization requirements?
- Are message contracts appropriate for the
service contract?
- What is the versioning strategy for contracts
and services?
Recommendations for these considerations are discussed in
the chapters entitled “Contracts” and “Versioning Strategies”.
Implementing a WCF Service
Once the service contract is defined, you implement it on a
service type. At a minimum, that means providing code for each method (service
operation) defined in the service contract to coordinate access to business
functionality. You should also consider what attributes should be applied to
the service type or its operations. Specifically, some service and operation
behaviors should be set in code – as opposed to declarative configuration, for
example:
- What instancing mode will be used?
- What concurrency mode will be used?
- Are transactions supported for this service
type?
- Should the service join a particular
synchronization context?
Example 2
illustrates the WCF service implementation for the service contract in Example
1. At a minimum the InstanceContextMode
should always be explicitly set to make it clear to developers which mode is
being used. The default is PerSession
which is typically not the desired setting.
Example 2: A simple
WCF service implementation
[ServiceBehavior(InstanceContextMode=InstanceContextMode.PerCall)]
public class SimpleService : ISimpleService
{
public string SendMessage(string message)
{
Console.WriteLine(message);
return string.Format("Message received: {0}", message);
}
}
Behavior configuration options are discussed in the chapter
on “Behaviors” and recommendations for specific types of behaviors are discussed
in the appropriate chapters of this whitepaper.
Hosting a WCF Service
Hosting a WCF service means selecting a hosting environment
and then configuring a ServiceHost
instance with endpoints and behaviors. The ServiceHost
type is the centerpiece for hosting any service type, regardless of the hosting
environment. Initializing the ServiceHost
elicits the following considerations:
- How many endpoints will be exposed for the
service type? What protocols will be supported?
- Will a metadata exchange endpoint be provided?
- Which service behaviors and endpoint behaviors
will be configured?
- What features will be configured
programmatically?
- What features will be configured declaratively?
- Will a custom and
be useful to encapsulate common initialization features?
Example 3
shows how to programmatically initialize the ServiceHost instance for the service defined in Example 2.
A single endpoint is exposed over TCP for the service contract shown in Example 1.
Example 3:
Initializing the ServiceHost programmatically
using (ServiceHost host = new ServiceHost(typeof(WcfServiceLibrary.SimpleService)))
{
host.AddServiceEndpoint(typeof(WcfServiceLibrary.ISimpleService), new
NetTcpBinding(), "net.tcp://localhost:9000/SimpleService");
host.Open();
Console.ReadLine();
}
Hosting recommendations are discussed in the “Hosting” chapter
of this whitepaper.
Generating Proxies
Clients use proxies to communicate with services. SvcUtil.exe
(SvcUtil), a command line utility, can be used to generate a proxy with
configuration equivalent to the service endpoint. But, if you have access to
the service metadata (the service contract and any related data contracts) in a
referenced assembly you can also use ChannelFactory<T>
to build a client channel (a proxy equivalent) to communicate with a service. Example 4
illustrates the code to call a service operation after generating a proxy.
Example 4: Calling a
service operation with a generated proxy
ServiceReferences.SimpleServiceClient proxy = new ServiceReferences.SimpleServiceClient();
string s = proxy.SendMessage("Hello from Test Client");
MessageBox.Show(s);
proxy.Close();
Whether you generate a proxy or write custom code there are
other client concerns that influence how you manage calls to services from the
client, including:
- How will collections and arrays be handled?
- Is a consistent object model desired to match
the service implementation?
- How will exceptions and faulted channels be managed?
- Are there multithreading considerations?
- Is UI responsiveness a concern?
These factors are discussed further in the “WCF Clients” chapter
of this whitepaper.
Assembly Allocation
It is helpful to have a plan up front for placing the parts
of a WCF application into assemblies. This makes projects more manageable,
easier to maintain, and if done right allows for sharing metadata assemblies
between services and clients. When you establish categories of assemblies with
an assembly naming convention up front, it also becomes easier to add new
functionality to an application.
Consider the list of assemblies in Example 5 that comprise those used by the articles manager
feature for a sample application. For this example the naming convention for
the assemblies uses the prefix WCFEssentials.ArticlesManager,
which is representative of the overall application name and the ArticlesManager
feature name. The assumption here is that every major feature in an application
will have assemblies for: data access, business logic, entities that are
serializable, contracts, services, a test host and a test client. In addition,
there may be shared entities and constants common to the overall application.
Example 5: Breakdown
of assemblies for the code sample
Of course there are some reasonable alternatives to this
assembly breakdown, such as:
- Flattening the service and business logic tier
into a single assembly – in this case WCFEssentials.ArticlesManager.Services. I
recommend this only if business logic will always be exposed through a service.
The service contract would still be separate so that metadata can be shared
without sharing implementation. In addition, within the flattened service
assembly I would still recommend separating service types from business types.
- Flattening all contracts into a single assembly
– WCFEssentials.Contracts. This is manageable if there are only a few services
in the application. In this case common contracts are also included in this
assembly.
- Flattening all entities into a single assembly –
WCFEssentials.Entities. This is manageable if there are only a few services in
the application. In this case common entities are also included in this assembly.
You should agree on your assembly allocation convention
during the design phase of your application in order to save time as you begin
to write code. The “WCF Clients” chapter of this whitepaper will also discuss
how this allocation can help you when sharing libraries between services and
clients.
Recommendations: Getting Started
- Define a naming convention and assembly
allocation plan during the design phase.
- Always put service implementations in a separate
assembly from service contracts and related metadata to support sharing
libraries with clients.
- Try to allocate separate assemblies for
services, contracts, entities and constants.
- Create a custom test host and test client for
every service.
Stay tuned as we post a series of chapters discussing
specific recommendations for contracts, bindings, behaviors, hosting, clients,
exception handling and more. Each short chapter will provide guidance and code
samples to illustrate that guidance for a particular topic.