Thursday, September 14, 2006 7:50 PM bart

Creating a WCF POP3 tunnel - Part 2 - A WCF service for POP3

Introduction

In the introductory post of this blog series, I explained our mission statement: the creation of a POP3 tunnel over SOAP/HTTP using WCF. The first part of the implementation was covered in part 1 of this blog series: "A simple POP3 client in C#". In this post, I'm going to focus on the WCF service that acts as a facade to a POP3 server behind the scenes.

Where are we?

The diagram below shows the piece of the solution we'll be focusing on in this post:

********************LOCALHOST*******************
*                                              *

* +-------------+              +-------------+ *
* | Mail client | -(TCP:110)-> | POP3 server | *
* +-------------+              +------+------+ *
*                                     |        *
*                              +------+------+ *
*                              | WCF client  | *
*                              +------+------+ *
*                                     |        *
**************************************|*********
                                      |
                                (HTTP/SOAP:80)
                                      |
******************TUNNEL SERVER*******|*********
*                                     |        *
* +--------------+             +------+------+ *
* | POP3 client  +-----<==-----+ WCF server  | *
* +------+-------+             +-------------+ *
*        |                                     *
*********|**************************************
         |
     (TCP:110)
         |
*********|********TARGET SERVER*****************
*        |                                     *
* +------+-------+                             *
* | POP3 server  +                             *
* +--------------+                             *
*                                              *
************************************************

Legend:

Gray  - External components
Blue  - Implemented in previous posts
Red   - Covered in this post
Green - To be covered in further posts

A WCF facade

In part 1 of this blog series, we created a simple Pop3Client class to communicate with a POP3 server in a command-oriented fashion. The next step - covered in this post - is to expose this POP3 client through web services, using a WCF facade.

In order to do this, you can change the Pop3Client project to a Console Application project (instead of a type library project) or create another project that references the Pop3Client library. In the end you'll have to create some kind of host application (console, Windows service, ...) that acts as a home for the WCF server. In this project, add a new WCF service:

Creating a WCF service consists of two steps. First, we'll create a service contract, in reality not much more than an interface decorated with the ServiceContractAttribute:

/// <summary>
///
Service interface for a WCF POP3 tunnel.
///
</summary>
[ServiceContract(SessionMode = SessionMode
.Required)]
public interface
IPop3TunnelService
{
   [OperationContract(IsInitiating = true
)]
   string Connect(string server, int
port);

   [
OperationContract
()]
   string User(string
user);

   [
OperationContract
()]
   string Pass(string
password);

   [
OperationContract
()]
   string
Noop();

   [
OperationContract
()]
   string
Rset();

   [
OperationContract
()]
   string
Stat();

   [
OperationContract(Name = "List"
)]
   string
List();

   [
OperationContract(Name = "List2"
)]
   string List(uint
msg);

   [
OperationContract
()]
   string Retr(uint
msg);

   [
OperationContract
()]
   string Dele(uint
msg);

   [
OperationContract
()]
   string Top(uint msg, uint
n);

   [
OperationContract(Name = "Uidl"
)]
   string
Uidl();

   [
OperationContract(Name = "Uidl2"
)]
   string Uidl(uint
msg);

   [
OperationContract
()]
   string Apop(string name, string
digest);

   [
OperationContract(IsTerminating = true
)]
   string
Quit();
}

There are a few important things to notice in this service contract:

  • Every operation is marked with an OperationContractAttribute.
  • Our service requires the use of sessions (SessionMode = SessionMode.Required). This will map one-on-one on a POP3 session.
  • The Connect method is marked as the (WCF) initiator of the session; the Quit method is marked as the terminator of the (WCF) session.
  • Operations with overloads specify an alternative Name property.

Implementing the WCF service

Next, we create an implementation for the WCF service (omitted code to log operations to the console, see download):

public class Pop3TunnelService : IPop3TunnelService
{
   private Pop3Client
conn;

   public string Connect(string server, int
port)
   {
      conn = new Pop3Client(server, port);
      return
conn.Connect().Message;
   }

   public string User(string user)
   {
      return
conn.User(user).Message;
   }

   public string Pass(string
password)
   {
      return
conn.Pass(password).Message;
   }

   public string
Noop()
   {
      return
conn.Noop().Message;
   }

   public string
Rset()
   {
      return
conn.Rset().Message;
   }

   public string
Stat()
   {
      return
conn.Stat().Message;
   }

   public string
List()
   {
      return
conn.List().Message;
   }

   public string List(uint
msg)
   {
      return
conn.List(msg).Message;
   }

   public string Dele(uint
msg)
   {
      return
conn.Dele(msg).Message;
   }

   public string Retr(uint msg)
   {
      return
conn.Retr(msg).Message;
   }

   public string Top(uint msg, uint n)
   {
      return
conn.Top(msg, n).Message;
   }

   public
string Uidl()
   {
      return
conn.Uidl().Message;
   }

   public string Uidl(uint
msg)
   {
      return
conn.Uidl(msg).Message;
   }

   public string Apop(string name, string
digest)
   {
      return
conn.Apop(name, digest).Message;
   }

   public string
Quit()
   {
      string result = conn.Quit().Message;
      conn.Close();

      return 
result;
   }
}

Time to create a service host. The WCF support in VS2005 provides us with a default implementation, but let's make a few changes:

internal class MyServiceHost
{
   internal static ServiceHost myServiceHost = null
;

   static
MyServiceHost()
   {
      Uri baseAddress = new Uri("http://localhost:8080/BdsSoft.Net.Mail/Pop3TunnelService"
);

      myServiceHost = new ServiceHost(typeof(Pop3TunnelService), baseAddress);
   }

   internal static void
StartService()
   {
      myServiceHost.Open();
   }

   internal static void
StopService()
   {
      if (myServiceHost.State != CommunicationState
.Closed)
         myServiceHost.Close();
   }
}

Notes:

For the sake of the demo, let's stick with a simple console-approach for our host application:

class Program
{
   public static void
Main()
   {
      Console.WriteLine("WCF POP3 Tunnel Server"
);
      Console.WriteLine("======================"
);
      Console
.WriteLine();

      Console.WriteLine("Initializing server tunnel endpoint... Listening on:"
);
      foreach (Uri uri in MyServiceHost
.myServiceHost.BaseAddresses)
        
Console.WriteLine("- {0}"
, uri.ToString());
      Console
.WriteLine();

       MyServiceHost
.StartService();
       Console
.ReadLine();
       MyServiceHost.StopService();
   }
}

A similar approach can be taken when defining a Windows Service to host the WCF service (OnStart does MyServiceHost.StartService() and OnStop does MyServiceHost.StopService()).

Configuration

In order to run the WCF host successfully, we need to make some configuration changes. In order to do this, add an app.config "Application Configuration File" to the project and add the following configuration to it:

<?xml version="1.0" encoding="utf-8" ?>
<
configuration
>
   <
system.serviceModel
>
      <
services
>
         <service name="BdsSoft.Net.Mail.Pop3TunnelService" behaviorConfiguration="returnFaults"
>
            <
endpoint contract="BdsSoft.Net.Mail.IPop3TunnelService" binding="wsHttpBinding"
/>
            <
endpoint contract="IMetadataExchange" binding="mexHttpBinding" address="mex"
/>
         </
service
>
      </
services
>
      <
behaviors
>
         <
serviceBehaviors
>
            <
behavior name="returnFaults"
>
               <
serviceDebug includeExceptionDetailInFaults="true"
/>
               <
serviceMetadata httpGetEnabled="true"
/>
            </
behavior
>
         </
serviceBehaviors
>
      </
behaviors
>
   </
system.serviceModel
>
</
configuration>

Double-check the underlined portions because these might be different depending on the (CLR) namespace you've chosen to put the WCF stuff in. For the next part of this blog series, I've added the metadata* and mex* stuff to allow discovery of our service (which will be required to create a proxy).

Make it run

Time to test our WCF service. Assuming you've created a console application, press F5 to start debugging. On Windows Vista things will go wrong at first sight:

The problem is that the application isn't able to register an HTTP listener (with http.sys) because of security restrictions. This causes WCF to throw the AddressAccessDeniedException with the message "HTTP could not register URL xyz. Your process does not have access rights to this namespace (see http://go.microsoft.com/fwlink/?LinkId=70353 for details)." This link does provide you with the information needed for Windows XP based on httpcfg.exe but for Windows Vista you will just find one line of information: "With Windows Vista, it is now possible to configure these settings through the Netsh.exe tool. These settings are part of the HTTP context of Netsh.exe.".

Let's take a look at the configuration in Windows Vista. Open a command prompt using "Run as Administrator" and start the netsh tool. Then switch to the http context and execute:

add urlacl url=http://+:8080/BdsSoft.Net.Mail/Pop3ListenerService user=Everyone

This will allow everyone (security risk: change this to the service account that will execute the POP3 WCF tunneling service) to register the URL for HTTP listening.

The screenshot below show the result:

 

Now the service should start successfully:

To confirm our service has started successfully, take a look in your browser:

Download the code

You can download the code for the WCF service over here. Make sure to check out part 1 of this series first to download the required Pop3Client implementation code.

All usual disclaimers apply. The writer of this blog doesn't make any warranties or guarantuees about the quality of this software, and isn't responsible for possible information loss under any circumstance whatsoever. The code on this blog post and in the download are made available for demonstration purposes only.

Next time, we'll start creating the client-side component to take the POP3 commands from the mail client and transform these into WCF calls. Stay tuned!

Del.icio.us | Digg It | Technorati | Blinklist | Furl | reddit | DotNetKicks

Filed under: ,

Comments

# Creating a WCF POP3 tunnel - Part 3 - A look on the client-side

Introduction In the introductory post of this blog series, I explained our mission statement: the creation...