I'm currently studying for the Microsoft .Net Framework 3.5 WCF certification exam. One of the concepts that we've been discussing around the office recently is how to properly consume self-developed WCF services. Over the past few weeks, Josh Heyse has hammered into my brain the fact that I should not being adding service references to services encapsulated within a solution.
Despite the fact that adding a service reference is incredibly easy, it can lead to complications if the service is modified or changed. Service references use metadata (wsdl) to regenerate service contracts and proxies. Because the service contracts in the client are actual copies, they don't automatically change when the original service contract changes. If my service definition changed while developing, I would have to force refreshes on my service references to generate updated proxies, and errors are harder to catch at compile time. Therefore, when you have access to the dll in which the ServiceContracts are defined, it is better to dynamically create your clients using a ChannelFactory and the ServiceContract definition.
In light of all this, I've decided to create a simple guide to dynamically create WCF clients using a ChannelFactory. In addition, I'm going to demonstrate how to create WCF clients based off of endpoint information stored in a configuration file.
Step 1: Create a Windows Class Library Project named Contracts . This is where we will store all of the ServiceContracts and DataContracts for our WCF service. In this example I'm going to create a simple math service, with the service contract defined by an interface named IMathService. Below is a copy of the code for this service definition.
2 namespace Contracts
3 {
4 [ServiceContract]
5 public interface IMathService
6 {
7 [OperationContract]
8 int AddInt(int a, int b);
9
10 [OperationContract]
11 int SubtractInt(int a, int b);
12
13 [OperationContract]
14 double AddDouble(double a, double b);
15
16 [OperationContract]
17 double SubtractDouble(double a, double b);
18 }
19 }
Step 2: In the same solution, create a WCF Service Library named SampleMathService. Add the Contracts project as a reference to this project. Delete the default Service1.cs and IService1.cs files. Instead, add a new class file with the name MathService.cs. In this new class, add "using Contracts;" to the top, and implement the IMathService interface. Below is a copy of the code:
1 namespace SampleMathService
2 {
3 public class MathService : IMathService
4 {
5 #region IMathService Members
6
7 public int AddInt(int a, int b)
8 {
9 return a + b;
10 }
11
12 public int SubtractInt(int a, int b)
13 {
14 return a - b;
15 }
16
17 public double AddDouble(double a, double b)
18 {
19 return a + b;
20 }
21
22 public double SubtractDouble(double a, double b)
23 {
24 return a - b;
25 }
26
27 #endregion
28 }
29 }
For this example, I configured the the MathService using the default wsHttpBinding and granted it the address of http://localhost:8888/MathService/. Below is the configuration file:
1 xml version="1.0" encoding="utf-8" ?>
2 <configuration>
3 <system.web>
4 <compilation debug="true" />
5 system.web>
6
8 <system.serviceModel>
9 <services>
10 <service behaviorConfiguration="SampleMathService.MathServiceBehavior"
11 name="SampleMathService.MathService">
12 <endpoint address="" binding="wsHttpBinding" contract="Contracts.IMathService">
13 <identity>
14 <dns value="localhost" />
15 identity>
16 endpoint>
17 <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
18 <host>
19 <baseAddresses>
20 <add baseAddress="http://localhost:8888/MathService/" />
21 baseAddresses>
22 host>
23 service>
24 services>
25 <behaviors>
26 <serviceBehaviors>
27 <behavior name="SampleMathService.MathServiceBehavior">
28 <serviceMetadata httpGetEnabled="true" />
29 <serviceDebug includeExceptionDetailInFaults="false" />
30 behavior>
31 serviceBehaviors>
32 behaviors>
33 system.serviceModel>
34 configuration>
There is an endpoint for the MetaData in this configuration, however we will not be using that since we have access to the ServiceContract definition (Contracts.IMathService)!
Step 3: Add a Windows Console Application to your project. Add the Contracts project as a reference to this project. In addition, add the System.ServiceModel library as a reference to this project. This is where we will create and test our dynamic client.
In order to allow for the service configuration to be modified outside of compiled code, add a configuration file to your project. Within this App.config file, we need to add the client and service endpoint information within the System.ServiceModel configuration section. My App.config is displayed below:
1 xml version="1.0" encoding="utf-8" ?>
2 <configuration>
3 <system.serviceModel>
4 <client>
5 <endpoint name="MathServiceEndpoint"
6 address="http://localhost:8888/MathService/"
7 contract="Contracts.IMathService"
8 binding="wsHttpBinding"/>
9 client>
10 system.serviceModel>
11 configuration>
I named my endpoint MathServiceEndpoint, and the address, contract and binding information are all defined for when we create our client dynamically. This is a useful step because it allows for us to change endpoint information in the future without having to modify any code.
We are now ready to go ahead and code our test client and application. The code to create a client dynamically is incredibly simple, especially since we have all of our endpoint information defined within our App.Config file. The System.ServiceModel namespace contains a ChannelFactory object, which allows for the creation of channels that can connect with the service endpoint. To create a create a client all we have to do is initalize a ChannelFactory that passes in the service's ServiceContract. Using this ChannelFactory, we can create a new channel to use as our proxy for communication with our WCF service. The code that demonstrates this is below:
1 ChannelFactory<IMathService> factory = new ChannelFactory<IMathService>("MathServiceEndpoint");
2
3 IMathService proxy = factory.CreateChannel();
Once we have created our proxy channel, we are able to call the OperationContracts defined in our IMathService ServiceContract. Below is a copy of my Console Application, which calls each of the OperationContracts in IMathService.
1 namespace MathClientTestApplication
2 {
3 class Program
4 {
5 static void Main(string[] args)
6 {
7 ChannelFactory<IMathService> factory = new ChannelFactory<IMathService>("MathServiceEndpoint");
8
9 factory.Open();
10
11 IMathService proxy = factory.CreateChannel();
12
13 // Test AddInt(int a, int b)
14 int addIntResult = proxy.AddInt(3, 2);
15 Console.WriteLine(string.Format("AddInt(3, 2) = {0}", addIntResult));
16
17 // Test SubtractInt(int a, int b)
18 int subtractIntResult = proxy.SubtractInt(3, 2);
19 Console.WriteLine(string.Format("SubtractInt(3, 2) = {0}", subtractIntResult));
20
21 // Test AddDouble(double a, double b)
22 double addDoubleResult = proxy.AddDouble(4.5, 5.5);
23 Console.WriteLine(string.Format("AddDouble(4.5, 5.5) = {0}", addDoubleResult));
24
25 // Test SubtractDouble(double a, double b)
26 double subtractDoubleResult = proxy.SubtractDouble(9.5, 6.1);
27 Console.WriteLine(string.Format("SubtractDouble(9.5, 6.1) = {0}", subtractDoubleResult));
28
29 factory.Close();
30
31 Console.ReadLine();
32
33 }
34 }
35 }
As you can see, I call each of the Operations that IMathService offers, and print the results to the console. In addition, I added factory.Open() and factory.Close() to explicitly indicate when creating channels is available and when the factory should be disposed.
I hope you enjoyed this simple guide to consuming WCF services dynamically! If you have any questions, feel free to leave them in the comments suggestion.