In this series of articles, we will discuss the major features of WCF:
Bindings and binding elements are the
connection between the application programming model, which includes the attributes and behaviors, and the
channel model, which includes the factories and listeners, message
encoders, and transport and protocol implementations. Typically, binding elements and bindings are implemented to enable channels to be used by
the application layer. ---
Microsoft
Through the paragraph above, Microsoft told us:
- Bindigs are Bridege or Connection between Programming Model and Channel Model
- Channel Model
- Factories
- Listeners
- Message Encoders
- Transport
- Protocol
We will discuss Bindings, actually, Channel Model in this article in the following order:
- Introduction
- Build App
- Build Service
- Build Host
- Build Clients
- Proxy Approach
- ChannelFactory Approach
- Run and Test the App
- Discussions
- Proxy vs. ChannelFactory
- Channel Stack
We will build an App to see some details of Channel Model for our discussion.
B: Build App --- Proxy vs. ChannelFactory
We use the current version of Visual Studio 2019 16.9.3 and the latest version of .NET Framework 4.8 to build the app.
We will make a VS solution named as
GettingStarted, with four projects, one WCF Service Library and three Console app (for details, see
here):
- WCF Service Library, named GettingStarted, hold the Service Contract
- Console, named GettingStartedHost, hosting the service
- Console, named GettingStartedClient, as a client to consume the service
- Console, named ChannelFactory, as another client to implement ChannelFactory
This is the Contract of the ABC of WCF Endpoints, we combine the two default classes in the WCF Service Library project into one, named as GettingStartedService.cs, including both the contracts (service contract and operation contract), and the implementations:
- using System.ServiceModel;
- using System;
-
-
-
-
- namespace GettingStartedLib
- {
- [ServiceContract(Namespace = "http://Microsoft.ServiceModel.Samples")]
- public interface ICalculator
- {
- [OperationContract]
- double Add(double n1, double n2);
- }
- public class CalculatorService : ICalculator
- {
- public double Add(double n1, double n2)
- {
- double result = n1 + n2;
- Console.WriteLine("Received Add({0},{1})", n1, n2);
-
- Console.WriteLine("Return: {0}", result);
- return result;
- }
- }
- }
App.config will configure the Address, Binding and Contract of the ABC of WCF Endpoints:
- <services>
- <service name="GettingStartedLib.CalculatorService">
- <host>
- <baseAddresses>
- <add baseAddress = "http://localhost:8000/GettingStarted/CalculatorService" />
- </baseAddresses>
- </host>
- <endpoint address="" binding="wsHttpBinding" contract="GettingStartedLib.ICalculator">
- <identity>
- <dns value="localhost"/>
- </identity>
- </endpoint>
- <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
- </service>
- </services>
where,
- service name: Contract name = "GettingStartedLib.CalculatorService"
- baseAddress: address = "http://localhost:8000/GettingStarted/CalculatorService"
- binding: binding = "wsHttpBinding"
2, Build Host
For the Host, we need to get a reference from the service project, and then step by step as explained in the code (in the file program.cs):
- using System;
- using System.ServiceModel;
- using System.ServiceModel.Description;
- using GettingStartedLib;
-
- namespace GettingStartedHost
- {
- class Program
- {
- static void Main(string[] args)
- {
-
- Uri baseAddress = new Uri("http://localhost:8000/GettingStarted/");
-
-
- ServiceHost selfHost = new ServiceHost(typeof(CalculatorService), baseAddress);
-
- try
- {
-
- selfHost.AddServiceEndpoint(typeof(ICalculator), new WSHttpBinding(), "CalculatorService");
-
-
- ServiceMetadataBehavior smb = new ServiceMetadataBehavior();
- smb.HttpGetEnabled = true;
- selfHost.Description.Behaviors.Add(smb);
-
-
- selfHost.Open();
- Console.WriteLine("The service is ready.");
-
-
- Console.WriteLine("Press <Enter> to terminate the service.");
- Console.WriteLine();
- Console.ReadLine();
- selfHost.Close();
- }
- catch (CommunicationException ce)
- {
- Console.WriteLine("An exception occurred: {0}", ce.Message);
- selfHost.Abort();
- }
- }
- }
- }
3, Build Clients
There are two approaches for client, we will discuss separately:
We need to Add a service reference to the calculator service to build the proxy:
-
In the Solution Explorer window, select the References folder under the GettingStartedClient project, and then select Add Service Reference from the shortcut menu.
-
In the Add Service Reference window, select Discover.
The CalculatorService service starts and Visual Studio displays it in the Services box.
-
Select CalculatorService to expand it and display the service contracts implemented by the service. Leave the default Namespace and choose OK.
Visual Studio adds a new item under the Connected Services folder in the GettingStartedClient project.
After adding this Service Reference, and build the app, we will see the App.config file has been updated, and one proxy (generated code) has been created:
App.Config
- <?xml version="1.0" encoding="utf-8" ?>
- <configuration>
- <startup>
- <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8" />
- </startup>
- <system.serviceModel>
- <bindings>
- <wsHttpBinding>
- <binding name="WSHttpBinding_ICalculator" />
- </wsHttpBinding>
- </bindings>
- <client>
- <endpoint address="http://localhost:8000/GettingStarted/CalculatorService"
- binding="wsHttpBinding" bindingConfiguration="WSHttpBinding_ICalculator"
- contract="ServiceReference.ICalculator" name="WSHttpBinding_ICalculator">
- <identity>
- <dns value="localhost" />
- </identity>
- </endpoint>
- </client>
- </system.serviceModel>
- </configuration>
where,
The serviceModel part has been added that includes the AB, and the name of C of the endpoints. Click the Service Reference, we can see the proxy class that includes the contract, and some additional operations for managing the proxy life cycle and the connection to the service.
-
-
-
-
-
-
-
-
-
-
- namespace GettingStartedClient.ServiceReference {
-
-
- [System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "4.0.0.0")]
- [System.ServiceModel.ServiceContractAttribute(Namespace="http://Microsoft.ServiceModel.Samples", ConfigurationName="ServiceReference.ICalculator")]
- public interface ICalculator {
-
- [System.ServiceModel.OperationContractAttribute(Action="http://Microsoft.ServiceModel.Samples/ICalculator/Add", ReplyAction="http://Microsoft.ServiceModel.Samples/ICalculator/AddResponse")]
- double Add(double n1, double n2);
-
- [System.ServiceModel.OperationContractAttribute(Action="http://Microsoft.ServiceModel.Samples/ICalculator/Add", ReplyAction="http://Microsoft.ServiceModel.Samples/ICalculator/AddResponse")]
- System.Threading.Tasks.Task<double> AddAsync(double n1, double n2);
-
- [System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "4.0.0.0")]
- public interface ICalculatorChannel : GettingStartedClient.ServiceReference.ICalculator, System.ServiceModel.IClientChannel {
- }
-
- [System.Diagnostics.DebuggerStepThroughAttribute()]
- [System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "4.0.0.0")]
- public partial class CalculatorClient : System.ServiceModel.ClientBase<GettingStartedClient.ServiceReference.ICalculator>, GettingStartedClient.ServiceReference.ICalculator {
-
- public CalculatorClient() {
- }
-
- public CalculatorClient(string endpointConfigurationName) :
- base(endpointConfigurationName) {
- }
-
- public CalculatorClient(string endpointConfigurationName, string remoteAddress) :
- base(endpointConfigurationName, remoteAddress) {
- }
-
- public CalculatorClient(string endpointConfigurationName, System.ServiceModel.EndpointAddress remoteAddress) :
- base(endpointConfigurationName, remoteAddress) {
- }
-
- public CalculatorClient(System.ServiceModel.Channels.Binding binding, System.ServiceModel.EndpointAddress remoteAddress) :
- base(binding, remoteAddress) {
- }
-
- public double Add(double n1, double n2) {
- return base.Channel.Add(n1, n2);
- }
-
- public System.Threading.Tasks.Task<double> AddAsync(double n1, double n2) {
- return base.Channel.AddAsync(n1, n2);
- }
- }
- }
Note
A ICalculatorChannel is created by the proxy class.
Then, finally, we have the client code in Program.cs, where using GettingStartedClient.ServiceReference, this is the reference for the proxy class; CalculatorClient class is created by Proxy:
- using System;
- using GettingStartedClient.ServiceReference;
-
- namespace GettingStartedClient
- {
- class Program
- {
- static void Main(string[] args)
- {
-
- CalculatorClient client = new CalculatorClient();
-
-
-
- double value1 = 100.00D;
- double value2 = 15.99D;
- double result = client.Add(value1, value2);
- Console.WriteLine("Add({0},{1}) = {2}", value1, value2, result);
-
-
- Console.WriteLine("\nPress <Enter> to terminate the client.");
- Console.ReadLine();
- client.Close();
- }
- }
- }
Now, build the app, we can run it, but we leave it to run with ChannelFactory appoach together.
2), ChannelFactory Approach
This code is simple, we do not need any reference, no change in app.config, we just need the ABC of Endpoints from service, and write the into the code:
- using System;
- using System.ServiceModel;
-
- namespace GettingStartedClient
- {
- [ServiceContract(Namespace = "http://Microsoft.ServiceModel.Samples")]
- public interface ICalculator
- {
- [OperationContract]
- double Add(double n1, double n2);
- }
-
- class Program
- {
- static void Main(string[] args)
- {
-
-
-
-
- WSHttpBinding myBinding = new WSHttpBinding();
- EndpointAddress myEndpoint = new EndpointAddress("http://localhost:8000/GettingStarted/CalculatorService");
- ChannelFactory<ICalculator> myChannelFactory = new ChannelFactory<ICalculator>(myBinding, myEndpoint);
-
-
- ICalculator client = myChannelFactory.CreateChannel();
-
-
-
- double value1 = 100.00D;
- double value2 = 15.99D;
- double result = client.Add(value1, value2);
- Console.WriteLine("Add({0},{1}) = {2}", value1, value2, result);
-
-
- Console.WriteLine("\nPress <Enter> to terminate the client.");
- Console.ReadLine();
- }
-
- }
- }
where the first part,
public interface ICalculator, that is the Contract; in the main, ChannelFactory create a Channel that includes all infor of all ABC of the endpoints.
4, Run and Test the App
We can test the app from either Visual Studio or from Command Prompt.
1), From Visual Studio
This approach is only working for the client built with proxy,
-
Open the solution with administrator right
-
Select the GettingStarted folder, and then select Set as Startup Project from the shortcut menu.
- From Startup Projects, select GettingStarted from the drop-down list, then select Run or press F5.
Note
In the original program, there are four operational methods, Add, SubTract, Multiply, and Divide, to make it simple, in this article, I only write one method, Add.
2), From Command Prompt
This approach is only working for the client built with proxy:
-
Open a command prompt as an administrator, and then navigate to your Visual Studio solution directory.
-
To start the service: Enter GettingStartedHost\bin\Debug\GettingStartedHost.exe. The server will be on, something like this
Server will be,
Client
1, Proxy vs. ChannelFactory
There are two approaches to implemente the WCF clients.
1), WCF Proxy approach
A WCF proxy is a CLR class that exposes the service
contract. A Service proxy class has the service contract operations and
some additional operations for managing the proxy life cycle and the
connection to the service.
There are two ways to create a WCF proxy as given below,
2), ChannelFactory approach
A channel factory creates channels of
different types that are used by client to send messages to the service.
ChannelFactory class is used with a known interface to create the
channel. This approach is commonly used when you have access control to
both the server and the client.
3), Proxy vs. ChannelFactory
2, Channel Stack
Now we know, even for the proxy approach, developer does not use and see Channel, but the proxy class makes one Channel for WCF operation.
In order to understand WCF Bindings in details, it's important to understand the Channel Stack as part of the WCF runtime.
WCF
binding is composed of binding elements and each binding element
correspond to a specific channel in the Channel Stack. The Channel Stack
can be categorized into two major areas i.e. Protocol Channels and
Transport Channels. Protocol Channels are
Transaction Protocol, Reliable Messaging Protocol and Security Protocol
while Transport Channels includes Message Encoding and Transport
Protocol.
- Channel Stack
- Protocol Channels
- Transaction Protocol
- Reliable Messaging Protocol
- Security Protocol
- Transport Channels
- Message Encoding
- Transport
Protocol.
Each
request coming from the client will go through a Channel Stack from top
to bottom and then an encoded byte stream message will travel over the
wire. On the other end, messages travel from the bottom to the top and
reaches the service as shown in the above diagram.
A complete picture of the WCF runtime with Service Instance, Dispatcher and Channel Stack is as follows:
Summary
This article gave a brief discussion of two WCF features:
- Discussions
- Proxy vs. ChannelFactory
- Channel Stack
References