Last updated .
Disclaimer: This is work in progress!
Clients can call services that run locally, on a different machine or in a different domain. Clients can remain agnostic of where the service is running, as long it is reachable. WCF services are commonly used in an intranet scenario to implement distributed applications. But it may as well be extended to serve clients across domain boundaries. For example, a WCF game service might serve some internet game app that runs on multiple computers, phones or other types of gadgets.
The WCF architecture promotes loose coupling, not only between between services and clients, but also for choice of communication protocol, message encoding formats and hosting environment. Out of the box, WCF supports the protocols HTTP, TCP, Named pipes and MSMQ.
I am going to demo a very basic "Hello World"-ish WCF sample. It will involve the service itself and a client that uses it. Specifically, the sample will be comprised of these four components:
First, we need a container for these components - a Visual Studio solution. So, in Visual Studio, create a new blank solution:
To create the project for the service contract, select the menu item in Visual Studio for creating a new project and select the
The project also contains a couple of code files. Delete the
IFirstWcfService.cs
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
using System.ServiceModel;
namespace FirstWcfServiceContract
{
[ServiceContract(Namespace = "http://firstwcfservice.org")]
public interface IFirstWcfService
{
[OperationContract]
string Capitalize(string value);
}
}
The
Next, we need a separate class library project to implement the service contract. I could use the same project template as before, but it should be clear by now that we got little benefit
from that - all we got was a reference to
In the new project, set a project reference to the
FirstWcfService.cs
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
using System.Text;
using FirstWcfServiceContract;
namespace FirstWcfServiceImpl
{
public class FirstWcfService : IFirstWcfService
{
public string Capitalize(string value)
{
char prev = ' ';
StringBuilder sb = new StringBuilder();
foreach (char c in value) {
if (char.IsLetter(c) && !char.IsLetter(prev))
sb.Append(char.ToUpper(c));
else
sb.Append(c);
prev = c;
}
return sb.ToString();
}
}
}
The lesson to take away from this class implementation is not the code itself, but the fact that there is nothing "WCF" about this; it is just a normal .Net class that implements an interface.
Third item on the project list is a host for the service. In order for the service to be callable, there must be a running process that can expose the service to the world. There
are various options for hosting a WCF service. We are going to keep things as simple as possible for now, and make a standard console app to host the service we just implemented.
So, right-click on the solution in solution explorer and go
In the new project, set a project reference to the
Program.cs
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
using System;
using System.ServiceModel;
using FirstWcfServiceContract;
using FirstWcfServiceImpl;
namespace FirstWcfServiceHost
{
class Program
{
static void Main(string[] args)
{
const string baseAddress = "http://localhost:9001/FirstWcf";
using (ServiceHost host = new ServiceHost(typeof(FirstWcfService), new Uri(baseAddress)))
{
host.AddServiceEndpoint(typeof(IFirstWcfService), new BasicHttpBinding(), "FirstWcfService");
host.Open();
Console.WriteLine("Press Enter to terminate.");
Console.ReadLine();
}
}
}
}
The above code creates a
With a live host, an endpoint is added. At least one endpoint is needed before a client can call the service. The endpoint is configured through a call to
When the host program is run, the service host is first initialized and immediately after that, the main thread is blocked in call to
To test the host application, right-click the
System.ServiceModel.AddressAccessDeniedException
"The process has no rights to this namespace."
The solution is to close Visual Studio and restart as an administrator. Now, the application can be started with no problems:
We now have a running service. The next step is to create a client app to consume the service.
Add a fourth project to the solution. Select the same
Change the code in
Program.cs
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
using System;
using System.ServiceModel;
using FirstWcfServiceContract;
namespace FirstWcfServiceClient
{
class Program
{
static void Main(string[] args)
{
const string serviceAddress = "http://localhost:9001/FirstWcf/FirstWcfService";
EndpointAddress endpoint = new EndpointAddress(serviceAddress);
IFirstWcfService proxy = ChannelFactory<IFirstWcfService>.CreateChannel(new BasicHttpBinding(), endpoint);
string output = proxy.Capitalize("test input.test input\r\ntest input.");
Console.WriteLine("Service returned '{0}'.", output);
Console.WriteLine("Press Enter to terminate client.");
Console.ReadLine();
}
}
}
The above code uses the static
As shown in the client console, the service
The above sample solution showcased building a very simple service, together with a host and a client application. All this was partitioned into four assemblies. It's pretty obvious that the client should be separate from the service. But the sample also has the service as a separate assembly from the host. This is the preferred setup that constitutes the least coupling between host and service and also has the practical implication that the service can be moved to a different type of host with no implications for clients. The sample also defined the service contract (the set of interfaces and their supporting types) in an assembly of its own. Again, this is to decouple the service contract from its implementation. Remember that the client is dependent on the contract, not the implementation, so there is no need to distribute the service assembly to clients. (Actually there is no need to distribute the contact either, I'll come to that). Also, having the two in separate assemblies promotes the idea that the same contract could well be implemented by multiple services. Each of those could be using different endpoints, bindings and policies.
In fact, I would argue that the business functionality should also be distilled into its own assembly or set of assemblies. In the above example, the "business" functionality was extremely simple, only involving the processing of a string value, but still, the code for this should have been moved into its own assembly. This way, the business functionality can be reused by clients that may not need to go through a service - clients internal to the application for example. In addition, such a division between service and business component makes the two testable as separate entities. It also makes it possible to version the service independently of the business component. testable as separate entities
I hope that the simple sample developed in this chapter has conveyed that a number of concepts are involved in a WCF solution.
WCF clients and services use the types exposed by the
The client code that calls the service method looks like any other .Net method call. But, in fact, it's quite different. Under the hood, the servicemodel has to serialize the method call, including parameter values and then pass this packet of data over the wire. At the receiving end, the packet must be deserialized and converted into a stack frame that invokes a method. In a similar manner, the method return value is serialized at the service end and then deserialized at the client. In order for this to work, the client and the service have to agree in detail on how messages are formatted. To maximize interoperability between disparate systems, the message is usually serialized to XML that adheres to a common XSD schema.
In WCF, a service is a type adorned with the
In this chapter's example, we developed our own host application, which is an executable that uses the
No matter which hosting option is used, hosting a service ultimately comes down to instantiating a
An endpoint is what makes a service reachable, in that it's configured with:
A host exposes one or more endpoints. The same contract can be make available through multiple bindings and / or adresses.
A service address is a URI, which has the format
A binding describes the protocols used by the service, specifically:
Clients need information about the service in order to use it. A client can obtain metadata at runtime by invoking a metadata exchange endpoint if the service exposes one. Or metadata can be extracted from a service in the form of a WSDL document, which is XML according to a standard schema. In the example above we used neither of these options. Instead, the client obtained information about the contrat by referencing the assembly containing the service interface, and in addition we simply hardcoded the address and binding according to what the service supported.
A proxy is a type that implements the same interface as the service, thus acting as a facade to the real service. The client application uses a proxy to communicate with the service. Behind the curtain, the generated proxy class uses a channel to communicate with the service.
Communication between client and service goes through a channel. The service host creates a channel listener for an endpoint. At either end, the channel is actually a layered stack of message processors, for example there is a transport layer, a message-encoding layer and other probably further layers related to security, for example.
A behavior further affects how a message is processed. Behaviors are local to a service or a client and thus do not form part of service metadata. Examples of behaviors include features such as authentication or transactions.
Get the code for this chapter's example as a Zip file
In the first sample developed above, there were no configuration files in use. Addresses, bindings and such were hardcoded in the service host. These things are usually factored
out of the code and into configuration files. And with good reason: Using configuration files makes the services more maintainable and amenable to testing. So let's change the projects
to use configuration. I started out by copying the whole solution and renamed the new solution file
After opening that new solution, we need to add a config file to the
Start off by adding configuration for our service: select the
The next step in the wizard is about selecting a binding for the service. Let's go for TCP:
Click
app.config
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<system.serviceModel>
<services>
<service name="FirstWcfServiceImpl.FirstWcfService">
<endpoint address="net.tcp://localhost:9001/FirstWcf/FirstWcfService"
binding="netTcpBinding" bindingConfiguration="" contract="FirstWcfServiceContract.IFirstWcfService" />
</service>
</services>
</system.serviceModel>
</configuration>
All configuration in related to WCF goes inside a
app.config
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<system.serviceModel>
<services>
<service name="FirstWcfServiceImpl.FirstWcfService">
<endpoint address="FirstWcfService" name="tcpEndpoint"
binding="netTcpBinding" bindingConfiguration="" contract="FirstWcfServiceContract.IFirstWcfService" />
<host>
<baseAddresses>
<add baseAddress="net.tcp://localhost:9001/FirstWcf"/>
</baseAddresses>
</host>
</service>
</services>
</system.serviceModel>
</configuration>
Now, the base address has been factored out into a
app.config
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior name="mexBehavior">
<serviceMetadata />
</behavior>
</serviceBehaviors>
</behaviors>
<services>
<service name="FirstWcfServiceImpl.FirstWcfService" behaviorConfiguration="mexBehavior">
<endpoint address="FirstWcfService" name="tcpEndpoint"
binding="netTcpBinding" bindingConfiguration="" contract="FirstWcfServiceContract.IFirstWcfService" />
<endpoint binding="mexTcpBinding" name="mex" contract="IMetadataExchange" />
<host>
<baseAddresses>
<add baseAddress="net.tcp://localhost:9001/FirstWcf"/>
</baseAddresses>
</host>
</service>
</services>
</system.serviceModel>
</configuration>
Now, there is a new
We can now modify the code for the host project. Details about addresses and bindings now need to go, because that is all defined in configuration now. So, the somewhat leaner code for the host now looks like this:
Program.cs
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
using System;
using System.ServiceModel;
using FirstWcfServiceImpl;
namespace FirstWcfServiceHost
{
class Program
{
static void Main(string[] args)
{
using (ServiceHost host = new ServiceHost(typeof(FirstWcfService)))
{
host.Open();
Console.WriteLine("Press Enter to terminate.");
Console.ReadLine();
}
}
}
}
Comparing this code with its previous version (see above), it is evident that there is no more mention of addresses and no endpoint is created. The service model now creates the endpoint for
us, thanks to the configuration. The only value we provide in instantiating the host is the type of
We can now start modifying the client project as well to get metadata info from the service instead of hardcoding values for address and binding type and instead
of referencing the assembly with the service contract directly. In the
WCF comes with a command-line tool called
Then, open a Visual Studio command prompt and type the following command to invoke
svcutil /d:{path to project folder} /o:FirstWcfServiceProxy.cs
/namespace:*,FirstWcfService /config:app.config net.tcp://localhost:9001/FirstWcf
For the
When this command is run,
The interface that the service implements has been regenerated as such:
FirstWcfServiceProxy.cs (partial)
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
[System.ServiceModel.ServiceContractAttribute(Namespace="http://firstwcfservice.org",
ConfigurationName="FirstWcfService.IFirstWcfService")]
public interface IFirstWcfService
{
[System.ServiceModel.OperationContractAttribute(Action="http://firstwcfservice.org/IFirstWcfService/Capitalize",
ReplyAction="http://firstwcfservice.org/IFirstWcfService/CapitalizeResponse")]
string Capitalize(string value);
[System.ServiceModel.OperationContractAttribute(Action="http://firstwcfservice.org/IFirstWcfService/Capitalize",
ReplyAction="http://firstwcfservice.org/IFirstWcfService/CapitalizeResponse")]
System.Threading.Tasks.Task<string> CapitalizeAsync(string value);
}
As you can see, in addition to the
FirstWcfServiceProxy.cs (partial)
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
public partial class FirstWcfServiceClient : System.ServiceModel.ClientBase<FirstWcfService.IFirstWcfService>,
FirstWcfService.IFirstWcfService
{
// Constructors left out for brevity
public string Capitalize(string value)
{
return base.Channel.Capitalize(value);
}
public System.Threading.Tasks.Task<string> CapitalizeAsync(string value)
{
return base.Channel.CapitalizeAsync(value);
}
}
The
Program.cs
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
using System;
namespace FirstWcfServiceClient
{
class Program
{
static void Main(string[] args)
{
FirstWcfService.IFirstWcfService proxy = new FirstWcfService.FirstWcfServiceClient();
string output = proxy.Capitalize("test input.test input\r\ntest input.");
Console.WriteLine("Service returned '{0}'.", output);
Console.WriteLine("Press Enter to terminate client.");
Console.ReadLine();
}
}
}
Gone are the explicit calls to create and endpoint and a channel. The client exe can now be started. It gives the same output in the console as in the previous sample:
The call
app.config
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<system.serviceModel>
<bindings>
<netTcpBinding>
<binding name="tcpEndpoint" />
</netTcpBinding>
</bindings>
<client>
<endpoint address="net.tcp://localhost:9001/FirstWcf/FirstWcfService"
binding="netTcpBinding" bindingConfiguration="tcpEndpoint"
contract="FirstWcfService.IFirstWcfService" name="tcpEndpoint">
</endpoint>
</client>
</system.serviceModel>
</configuration>
2016 by Niels Hede Pedersen