Introduction
The reason for publishing this article is that I think that a lot of people are going to use the new .NET 3.0 WCF. It is simple to use, and seems very powerful. I have spend a lot of time debugging this program, and I hope that this article will prevent other people from getting the same headache that I had because of a funny security exception that I would get all the time. Use the code directly or as an inspiration. I assume that the reader is familiar with client/server coding, and will therefore not get into details. It is said that WCF is very dynamic regarding the transfer methods, and can be configured to use almost any communication standard which makes it suitable for many client/server applications. It does not matter if you use HTTP, TCP etc., to transfer data, and the optional SSL / Encryption makes the WCF even more suited for large scale solutions.
Background
The project is straightforward, really. The source contains some files that I used to develop the application. The source contains the ServiceLibrary that is the contract compiled into a simple DLL. Don't confuse the difference between the contract and the proxy. The contract is a sort of a way to communicate, and the proxy is a way to get access to another remote contract. The other two libraries are almost self-explaining, the WCFClient and the WCFService. The goal was to create a dynamic client server solution that is not bound by configuration files etc., but is more dynamic. Plus don't underestimate the fun writing the code :) Thanks to Gedebuk, Denmark.
The Proxy.cs code is auto-generated by svcutil.exe. If you need to re-create the Proxy.cs, start the service and run the command: "C:\Program Files\Microsoft SDKs\Windows\v6.0\Bin\svcutil.exe" http://localhost:8001/MyService /out:Proxy.cs.
Using the code
The code consists of two folders that are loaded into one project file.
How did I create the project?
In this section, I will describe how I created the project. The steps are listed in sequence and are numbered.
1) Creating the ServiceLibrary.cs
I created the project by starting off with the ServiceLibrary
. This library is compiled into a DLL which is used on the client (or clients for that matter). In WCF terms, this is the contract that the client must obey in order to be able to communicate with the server. This contract is in fact an interface that the client(s) communicates through. The ServiceLibrary
contains all the methods that can be called by the client. In this example, it also holds the implementation for the service. The DataContract
has the mark [DataContract]
that indicates that it's an object that can be transferred. In WCF, it does not matter if it is a simple type, like a string, or a complex type like an object that is transferred over the wire. Below is a copy/paste from my contract.
Collapse using System;
using System.Collections.Generic;
using System.Text;
using System.ServiceModel;
using System.Runtime.Serialization;
namespace ServiceLibrary
{
[ServiceContract()]
publicinterface IService1
{
[OperationContract]
string MyOperation1(string myValue);
[OperationContract]
string MyOperation2(DataContract1 dataContractValue);
[OperationContract]
string HelloWorld(string str);
}
publicclass service1 : IService1
{
publicstring MyOperation1(string myValue)
{
return"Hello: " + myValue;
}
publicstring MyOperation2(DataContract1 dataContractValue)
{
return"Hello: " + dataContractValue.FirstName;
}
publicstring HelloWorld(string str)
{
return"Helloworld from " + str;
}
}
[DataContract]
publicclass DataContract1
{
string firstName;
string lastName;
[DataMember]
publicstring FirstName
{
get { return firstName; }
set { firstName = value; }
}
[DataMember]
publicstring LastName
{
get { return lastName; }
set { lastName = value; }
}
}
}
The code is something that the Visual Studio project generates for me. I've only added the HelloWorld part of this code. The VS environment did also generate a how-to on Adding a Service Reference. I've skipped that and deleted those parts. You can either configure the program from a configuration file that is a part of Visual Studio, or add the configuration in the code, or code the configuration. I have chosen to configure the service inside the code. One would argument about the ease of changing an XML file instead of recompiling the project if changes in the client/server relation occurs, but I have chosen to do this in order for the program to be able to configure itself in future implementations. This code will act as a core function in a rather large client/server solution where computers will contact other computers randomly via WCF. This requires the program to be able to change the runtime, and is the main reason for this code to be controlled in-code.
2) Creating the Service-Host Application
The service-host application is the program that holds the service that has the actual objects. This is the server if you like to call it that.
I created a Windows Forms project and added the following code:
Collapse using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Net;
using System.ServiceModel;
using System.ServiceModel.Description;
namespace WCFService
{
public partial class MainForm : Form
{
private ServiceHost host = null;
privatestring urlMeta, urlService = "";
public MainForm()
{
InitializeComponent();
Append("Program started ...");
}
void host_Opening(object sender, EventArgs e)
{
Append("Service opening ... Stand by");
}
privatevoid button1_Click(object sender, EventArgs e)
{
try
{
IPHostEntry ips = Dns.GetHostEntry(Dns.GetHostName());
IPAddress _ipAddress = ips.AddressList[0];
urlService = "net.tcp://" +
_ipAddress.ToString() + ":8000/MyService";
host = new ServiceHost(typeof(ServiceLibrary.service1));
host.Opening += new EventHandler(host_Opening);
host.Opened += new EventHandler(host_Opened);
host.Closing += new EventHandler(host_Closing);
host.Closed += new EventHandler(host_Closed);
NetTcpBinding tcpBinding = new NetTcpBinding();
tcpBinding.TransactionFlow = false;
tcpBinding.Security.Transport.ProtectionLevel =
System.Net.Security.ProtectionLevel.EncryptAndSign;
tcpBinding.Security.Transport.ClientCredentialType =
TcpClientCredentialType.Windows;
tcpBinding.Security.Mode = SecurityMode.None;
host.AddServiceEndpoint(typeof(
ServiceLibrary.IService1), tcpBinding, urlService);
ServiceMetadataBehavior metadataBehavior;
metadataBehavior =
host.Description.Behaviors.Find<ServiceMetadataBehavior>();
if (metadataBehavior == null)
{
metadataBehavior = new ServiceMetadataBehavior();
metadataBehavior.HttpGetUrl = new Uri("http://" +
_ipAddress.ToString() + ":8001/MyService");
metadataBehavior.HttpGetEnabled = true;
metadataBehavior.ToString();
host.Description.Behaviors.Add(metadataBehavior);
urlMeta = metadataBehavior.HttpGetUrl.ToString();
}
host.Open();
}
catch (Exception ex1)
{
Console.WriteLine(ex1.StackTrace);
}
}
privatevoid button2_Click(object sender, EventArgs e)
{
host.Close();
}
void host_Closed(object sender, EventArgs e)
{
Append("Service closed");
}
void host_Closing(object sender, EventArgs e)
{
Append("Service closing ... stand by");
}
void host_Opened(object sender, EventArgs e)
{
Append("Service opened.");
Append("Service URL:\t" + urlService);
Append("Meta URL:\t" + urlMeta + " (Not that relevant)");
Append("Waiting for clients...");
}
privatevoid Append(string str)
{
textBox1.AppendText("\r\n" + str);
}
}
}
The interesting part is of course the button1_Click
which creates the service and makes the service public to other clients. Now for other clients to connect to this service, the service needs a contract. The contract is not something that one would write themselves, and that's why I use the scvutil.exe tool. On my PC, the tool is located at C:\Program Files\Microsoft SDKs\Windows\v6.0\Bin\svcutil.exe. In order to make the contract object (that is actually the proxy that clients connect to), we need to generate Proxy.cs (the name of the file is not relevant at all, could be foo.cs as well). This will happen in step 3.
3) Building the Proxy Object for Clients to Use
The proxy object is built from the service description that is located on port 8001 (check the code). It reads the meta data from the service, and constructs the contract that is needed by clients when they want to communicate with the service.
- First, start the service and hit the "Start service" button. This creates the service. All meta info is published for other clients or tools to read them.
- Second, run "C:\Program Files\Microsoft SDKs\Windows\v6.0\Bin\svcutil.exe" http://localhost:8001/MyService /out:Proxy.cs. This creates two files. A Proxy.cs file and an XML config file. I discard the config file because I'll configure the service inside the program. You should consider putting the "C:\Program Files\Microsoft SDKs\Windows\v6.0\Bin\svcutil.exe" http://localhost:8001/MyService /out:Proxy.cs in a bat file to make it easier for yourself. I have included my bat file for you to see. It resides in the "ProxyGenerator" folder.
Next, I create a simple client and add relevant code to it.
4) Creating a Client Application and Adding Relevant Code to it
Now, we create a simple standard client program as a Windows Forms apllication. Once the files are generated (Proxy.cs and output.config <- can be suppressed), I add the Proxy.cs to my client program which is called WCFClient. Now, the client knows the contract and is able to obey it in order to create a communication channel to the service. One could compile the Proxy.cs into a DLL and simply add the DLL to the client (a cleaner way to add something that is crucial as a communication contract or interface, I think). For demonstration, we leave the Proxy.cs as it is and add it to the client project. Next, and the last step: we need to add some simple client code in order to retrieve the service proxy object. My client code looks like this:
Collapse using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.ServiceModel;
namespace WCFClient
{
public partial class Form1 : Form
{
string endPointAddr = "";
public Form1()
{
InitializeComponent();
}
privatevoid button1_Click(object sender, EventArgs e)
{
if (textBox1.Text != "")
{
endPointAddr = "net.tcp://" + textBox2.Text +
":8000/MyService";
NetTcpBinding tcpBinding = new NetTcpBinding();
tcpBinding.TransactionFlow = false;
tcpBinding.Security.Transport.ProtectionLevel =
System.Net.Security.ProtectionLevel.EncryptAndSign;
tcpBinding.Security.Transport.ClientCredentialType =
TcpClientCredentialType.Windows;
tcpBinding.Security.Mode = SecurityMode.None;
EndpointAddress endpointAddress =
new EndpointAddress(endPointAddr);
Append("Attempt to connect to: " + endPointAddr);
IService1 proxy =
ChannelFactory<IService1>.CreateChannel(
tcpBinding, endpointAddress);
using (proxy as IDisposable)
{
Append("Message from server: " +
(proxy.HelloWorld(textBox1.Text) +
" back to you :)"));
}
}
}
privatevoid Append(string str)
{
textBox3.AppendText("\r\n" + str);
Points of Interest
Well, I really had a lot of fun working with the code and writing the article. The most impressive thing, in my opinion, is that the transport layer very easily can be modified to use HTTP instead of TCP. I did not point that out in the article but that's also something nice to have. Being able to switch the transport layer from TCP (Secure SSL) into HTTP with some simple code, that's amazing! I did have some problems, though. The tcpBinding.Security.Mode = SecurityMode.None;
is very crucial on both sides. I'm not sure what it does, but it does not turn off the security completely as I have read in my references. There is still SSL encryption, but on a lower level. It is possible to add certificates to the connection, which also makes the connection more secure.
History
- December 13th 2006 - Project completed.
Bo Hansen
| Click here to view Bo Hansen's online profile. |