Entries:
Comments:
Posts:

Loading User Information from Channel 9

Something went wrong getting user information from Channel 9

Latest Achievement:

Loading User Information from MSDN

Something went wrong getting user information from MSDN

Visual Studio Achievements

Latest Achievement:

Loading Visual Studio Achievements

Something went wrong getting the Visual Studio Achievements

Outlook Webmail Add-in for Windows Home Server

Office_MAIL In this article, Brian Peek will demonstrate how to create an add-in for Windows Home Server that will allow users to view their Outlook mail from a web browser.
ASPSOFT, Inc.

Difficulty: Intermediate
Time Required: 2-3 hours
Cost: Free
Software: Windows Home ServerMicrosoft Outlook 2000/XP/2003/2007 (not Outlook Express/Windows Mail), Visual Basic or Visual C# Express Editions, Visual Web Developer Express Edition, Microsoft .NET Framework 3.0 runtime, Outlook/Office Primary Interop Assemblies (more on this below), Windows SDK (Vista or later, which includes the .NET 3.0 bits.  Note that this installs and works just fine under Windows XP.  The SDK can build applications for Windows XP and greater.)
Hardware: None
Download: CodePlex Project

Introduction

Windows Home Server is a new product from Microsoft which allows home users to manage and share data, including photos, documents, videos, music, etc.  It also provides a very easy way to backup all computers on your home network to a central storage server.

Windows Home Server can also be extended via add-ins to enhance the experience and provide new and interesting functionality other than what comes in the box.

One feature not present in WHS that I would find useful is the ability to view my Outlook mail box from the web at any time.  I have 6 or 7 email accounts that are all setup to retrieve via POP3 to Outlook.  Most of these accounts do not support IMAP or have a web-based interface.  Therefore, Outlook is generally open all day and checking messages.  When I'm away from home for work or pleasure, it's very often inconvenient to have to remote desktop into the machine with Outlook running to read my email, so it would be nice to have a web-based version of my current Outlook folders so I can view all email (old and new) at any time simply by browsing to a web server at home.  Windows Home Server comes with Internet Information Services 6 (IIS6) and one can easily add a new web application to IIS on the server.

So, this article will attempt to show how to build a new web site using ASP.NET that can be added to your Windows Home Server installation that will allow one to view the Outlook folders running on whatever computer contains your current Outlook installation and message store.

If you wish to just use the application, download the sample from above and skip down to the deployment section for installation instructions.

NOTE: This application will only work with Microsoft Outlook.  It will not work with Outlook Express, Windows Mail, or any other mail client.

Setup

Setup can be a bit tricky.  Office/Outlook will need to be installed on your development machine.  It does not need to be the same machine which contains your store at this point, but that too would help.  Once Office/Outlook is installed, the Primary Interop Assemblies for the version of Office you are using need to be installed.  For Office 2003/2007, this can be done choosing .NET Programming Support from the list of sub-items in the Microsoft Office Outlook section of the setup program.

2003

pia

Unfortunately I do not have an earlier copy of Office with which to check, but the procedure should be the same.  If anyone happens to try this with Office XP/2000, please let me know if/how it works.

If you will be developing on an OS earlier than Vista, install the .NET Framework 3.0 runtime.

Finally, install the Windows SDK linked above accepting all defaults.

Architecture

The architecture we will be using is very similar that to an N-tier application.  The machine running Outlook with the message store to be viewed is, in essence, the server machine.  That machine will run a host process that we will develop which will expose several methods via Windows Communication Foundation.  These methods will be consumed by an ASP.NET application running on the Windows Home Server.

The Host

Let's start by building the host application.  This will run on the computer where Outlook is installed and the messages are stored.  The application will be written to run in the notification area next to the Windows system clock.

To start, create a new Windows Application project named WHSMailHost.  Rename the default Form1.cs/.vb file to frmMain.cs/.vb.  Double-click on the file in the Solution Explorer to bring up the design surface.

In order to get the application to run in the notification area, drag and drop a NotifyIcon control from the Toolbox to the design surface, name it niIcon.  Also, drag over a ContextMenuStrip to the design surface and name it cmsMenu.  This will be used to pop up a context menu when the icon in the notification area is clicked.  Finally, set the following properties on the niIcon control:

Text WHS Mail Host
ContextMenuStrip cmsMenu
Visible True

Select the cmsMenu control and add a single menu item named Exit to the list.  Double-click on that menu item to create a default Click event.  In the code for the Click event, simply close the form as follows:

C#

private void mnuExit_Click(object sender, EventArgs e)
{
    // exit the application
    this.Close();
}

VB

Private Sub mnuExit_Click(ByVal sender As Object, ByVal e As EventArgs) Handles mnuExit.Click
    ' exit the application
    Me.Close()
End Sub

Finally, add the following events to frmMain by selecting them from the Event Property window and implementing them with the following code:

C#

private void frmMain_Resize(object sender, EventArgs e)
{
    // hide the form
    this.Hide();
}

private void frmMain_Load(object sender, EventArgs e)
{
    // start the WCF service
    MyServiceHost.StartService();
}

private void frmMain_FormClosing(object sender, FormClosingEventArgs e)
{
    // stop the WCF service
    MyServiceHost.StopService();
}

VB

Private Sub frmMain_Resize(ByVal sender As Object, ByVal e As EventArgs) Handles MyBase.Resize
    ' hide the form
    Me.Hide()
End Sub

Private Sub frmMain_Load(ByVal sender As Object, ByVal e As EventArgs) Handles MyBase.Load
    ' start the WCF service
    MyServiceHost.StartService()
End Sub

Private Sub frmMain_FormClosing(ByVal sender As Object, ByVal e As FormClosingEventArgs) Handles MyBase.FormClosing
    ' stop the WCF service
    MyServiceHost.StopService()
End Sub

The code above references a class named MyServiceHost.  This is what starts the WCF host and will be discussed next.

 

Windows Communication Foundation (WCF)

Windows Communication Foundation (formerly known as Indigo) is a feature of the .NET Framework 3.0 that allows one to build and run connected systems.  The simplest definition that fits with what we will be doing here is that it allows an application running on one machine (the client, in this case, the ASP.NET application) to execute a method on another machine (the host, in this case, the application we're currently building).

First, add a reference to the System.ServiceModel assembly.  Then, add a class to the project named WHSMailService.  Open the class and add the following code beneath the generated WHSMailService class implementation:

C#

internal class MyServiceHost
{
    internal static ServiceHost myServiceHost = null;

    internal static void StartService()
    {
        // Instantiate new ServiceHost 
        myServiceHost = new ServiceHost(typeof(WHSMailService));
        myServiceHost.Open();
    }

    internal static void StopService()
    {
        // Call StopService from your shutdown logic (i.e. dispose method)
        if (myServiceHost.State != CommunicationState.Closed)
            myServiceHost.Close();
    }
}

VB

Friend Class MyServiceHost
    Friend Shared myServiceHost As ServiceHost = Nothing

    Friend Shared Sub StartService()
        ' Instantiate new ServiceHost 
        myServiceHost = New ServiceHost(GetType(WHSMailService))
        myServiceHost.Open()
    End Sub

    Friend Shared Sub StopService()
        ' Call StopService from your shutdown logic (i.e. dispose method)
        If myServiceHost.State <> CommunicationState.Closed Then
            myServiceHost.Close()
        End If
    End Sub
End Class

This code simply creates a new WCF ServiceHost of the type WHSMailService (which we will implement next) and then opens that host so that it may receive incoming connections.  We will look at how this connections are configured later in the article.

Remember that the code in the form above above called the StartService and StopService methods located here when the main form loads and closes.  This very easily allows to immediately start the service host when the application starts and closes the service host when the application exits.

Contracts and Entities

A contract is an interface that defines which methods are exposed by the service host that can be consumed by the client application.  Both the client application and the server application will need to know what is in this interface, so we will need to create a second project that will contain the interface definition.  Additionally, we will need a way to pass folder and email information to and from each application, so we will define some custom classes to encapsulate those objects.

Create a new Class Library project in the current solution named WHSMailCommon.  In this new project, create a directory named Contracts and a directory named Entities.

Inside the Entities directory, create a new class named Folder.  This will represent an Outlook message folder.  The class should look like the following:

C#

using System;
using System.Collections.Generic;

namespace WHSMailCommon.Entities
{
    // entity object representing an Folder
    [Serializable]
    public class Folder : IComparable
    {
        private string _entryID;
        private string _name;
        private List<Folder> _folders;
        private int _unreadMessages;
        private int _totalMessages;

        public Folder(string entryID, string name, int unreadMessages, int totalMessages)
        {
            _entryID = entryID;
            _name = name;
            _unreadMessages = unreadMessages;
            _totalMessages = totalMessages;
        }

        // MAPI unique identifier
        public string EntryID
        {
            get { return _entryID; }
            set { _entryID = value; }
        }

        // subfolders of this folder
        public List<Folder> Folders
        {
            get { return _folders; }
            set { _folders = value; }
        }

        public string Name
        {
            get { return _name; }
            set { _name = value; }
        }

        public int UnreadMessages
        {
            get { return this._unreadMessages; }
            set { this._unreadMessages = value; }
        }

        public int TotalMessages
        {
            get { return this._totalMessages; }
            set { this._totalMessages = value; }
        }

        // used so we can sort the folders alphabetically later on
        public int CompareTo(object obj)
        {
            return string.Compare(this.Name, ((Folder)obj).Name);
        }
    }
}

VB

Imports Microsoft.VisualBasic
Imports System
Imports System.Collections.Generic

Namespace WHSMailCommon.Entities
    ' entity object representing an Folder
    <Serializable> _
    Public Class Folder
        Implements IComparable
        Private _entryID As String
        Private _name As String
        Private _folders As List(Of Folder)
        Private _unreadMessages As Integer
        Private _totalMessages As Integer

        Public Sub New(ByVal entryID As String, ByVal name As String, ByVal unreadMessages As Integer, ByVal totalMessages As Integer)
            _entryID = entryID
            _name = name
            _unreadMessages = unreadMessages
            _totalMessages = totalMessages
        End Sub

        ' MAPI unique identifier
        Public Property EntryID() As String
            Get
                Return _entryID
            End Get
            Set(ByVal value As String)
                _entryID = value
            End Set
        End Property

        ' subfolders of this folder
        Public Property Folders() As List(Of Folder)
            Get
                Return _folders
            End Get
            Set(ByVal value As List(Of Folder))
                _folders = value
            End Set
        End Property

        Public Property Name() As String
            Get
                Return _name
            End Get
            Set(ByVal value As String)
                _name = value
            End Set
        End Property

        Public Property UnreadMessages() As Integer
            Get
                Return Me._unreadMessages
            End Get
            Set(ByVal value As Integer)
                Me._unreadMessages = value
            End Set
        End Property

        Public Property TotalMessages() As Integer
            Get
                Return Me._totalMessages
            End Get
            Set(ByVal value As Integer)
                Me._totalMessages = value
            End Set
        End Property

        ' used so we can sort the folders alphabetically later on
        Public Function CompareTo(ByVal obj As Object) As Integer Implements IComparable.CompareTo
            Return String.Compare(Me.Name, (CType(obj, Folder)).Name)
        End Function
    End Class
End Namespace

This class defines several properties to describe the folder (EntryID, Name, etc.) and additionally implements the IComparable interface's CompareTo method so that we can easily sort the folders alphabetically later on.

Next, create a class named Email.  The code for this class looks like the following:

C#

using System;
using System.Collections.Generic;
using System.Text;

namespace WHSMailCommon.Entities
{
    // entity object representing an Email
    [Serializable]
    public class Email
    {
        private string _entryID;
        private string _from;
        private string _fromName;
        private string _subject;
        private DateTime _received;
        private int _size;
        private string _body;

        public Email(string entryID, string from, string fromName, string subject, DateTime received, int size)
        {
            _entryID = entryID;
            _from = from;
            _fromName = fromName;
            _subject = string.IsNullOrEmpty(subject) ? "(no subject)" : subject;
            _received = received;
            _size = size;
        }

        public Email(string entryID, string from, string fromName, string subject, DateTime received, int size, string body) :
            this(entryID, from, fromName, subject, received, size)
        {
            _body = body;
        }

        // MAPI unique ID
        public string EntryID
        {
            get { return _entryID; }
            set { _entryID = value; }
        }

        // email address of sender
        public string From
        {
            get { return _from; }
            set { _from = value; }
        }

        // name of sender
        public string FromName
        {
            get { return _fromName; }
            set { _fromName = value; }
        }

        public string Subject
        {
            get { return _subject; }
            set { _subject = value; }
        }

        public string Body
        {
            get { return _body; }
            set { _body = value; }
        }

        public DateTime Received
        {
            get { return _received; }
            set { _received = value; }
        }

        public int Size
        {
            get { return _size; }
            set { _size = value; }
        }
    }
}

VB

Imports Microsoft.VisualBasic
Imports System
Imports System.Collections.Generic
Imports System.Text

Namespace WHSMailCommon.Entities
    ' entity object representing an Email
    <Serializable> _
    Public Class Email
        Private _entryID As String
        Private _from As String
        Private _fromName As String
        Private _subject As String
        Private _received As DateTime
        Private _size As Integer
        Private _body As String

        Public Sub New(ByVal entryID As String, ByVal From As String, ByVal fromName As String, ByVal subject As String, ByVal received As DateTime, ByVal size As Integer)
            _entryID = entryID
            _from = From
            _fromName = fromName
            If String.IsNullOrEmpty(subject) Then
                _subject = "(no subject)"
            Else
                _subject = subject
            End If
            _received = received
            _size = size
        End Sub

        Public Sub New(ByVal entryID As String, ByVal From As String, ByVal fromName As String, ByVal subject As String, ByVal received As DateTime, ByVal size As Integer, ByVal body As String)
            Me.New(entryID, From, fromName, subject, received, size)
            _body = body
        End Sub

        ' MAPI unique ID
        Public Property EntryID() As String
            Get
                Return _entryID
            End Get
            Set(ByVal value As String)
                _entryID = value
            End Set
        End Property

        ' email address of sender
        Public Property From() As String
            Get
                Return _from
            End Get
            Set(ByVal value As String)
                _from = value
            End Set
        End Property

        ' name of sender
        Public Property FromName() As String
            Get
                Return _fromName
            End Get
            Set(ByVal value As String)
                _fromName = value
            End Set
        End Property

        Public Property Subject() As String
            Get
                Return _subject
            End Get
            Set(ByVal value As String)
                _subject = value
            End Set
        End Property

        Public Property Body() As String
            Get
                Return _body
            End Get
            Set(ByVal value As String)
                _body = value
            End Set
        End Property

        Public Property Received() As DateTime
            Get
                Return _received
            End Get
            Set(ByVal value As DateTime)
                _received = value
            End Set
        End Property

        Public Property Size() As Integer
            Get
                Return _size
            End Get
            Set(ByVal value As Integer)
                _size = value
            End Set
        End Property
    End Class
End Namespace

This class simply contains properties to describe an email message.

You will note that both of these classes have the [Serializable] attribute attached to them.  When objects are passed through WCF, they are serialized at the source and deserialized at the destination.  By marking the objects with the [Serializable] attribute, the .NET CLR can do this for us automatically  since we are not using any complex data types in our entities.

With our entities out of the way, we can define our contract.  Inside the Contracts directory, create a new Interface file named IWHSMailService.

This interface/contract will define three methods:  one method to return a tree of objects which represent the folder tree in Outlook (GetFolders), one method to return a list of messages inside that folder (GetMessages), and one method to return the contents of a specific message (GetMessages).  The interface will look like the following:

C#

using System.Collections.Generic;
using System.ServiceModel;
using WHSMailCommon.Entities;

namespace WHSMailCommon.Contracts
{
    // list of methods of the WHSMailService service
    [ServiceContract()]
    public interface IWHSMailService
    {
        [OperationContract]
        List<Folder> GetFolders();

        [OperationContract]
        List<Email> GetMessages(string entryID, int numPerPage, int pageNum);

        [OperationContract]
        Email GetMessage(string entryID);
    }
}

VB

Imports Microsoft.VisualBasic
Imports System.Collections.Generic
Imports System.ServiceModel
Imports WHSMailCommon.Entities

Namespace WHSMailCommon.Contracts
    ' list of methods of the WHSMailService service
    <ServiceContract()> _
    Public Interface IWHSMailService
        <OperationContract> _
        Function GetFolders() As List(Of Folder)

        <OperationContract> _
        Function GetMessages(ByVal entryID As String, ByVal numPerPage As Integer, ByVal pageNum As Integer) As List(Of Email)

        <OperationContract> _
        Function GetMessage(ByVal entryID As String) As Email
    End Interface
End Namespace

As with the entities, this interface is also decorated with several attributes.  First, any WCF contract interface must be tagged with the [ServiceContract()] attribute to define it as a contract to WCF.  Additionally, all methods which will be exposed for consumption by a client must be marked with the [OperationContract] attribute.

The implementation and description of these methods will come later when we write the contract implementation.

Configuration

The final thing to setup on the WCF server is the configuration file.  The service can very easily be configured using an application configuration file.  Add an Application Configuration file to the project named App.config.  Set the contents of the file to the following:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <system.serviceModel>
        <bindings>
            <netTcpBinding>
                <binding name="NewBinding0">
                    <security mode="None" />
                </binding>
            </netTcpBinding>
        </bindings>
        <services>
            <service name="WHSMailHost.WHSMailService">
                <endpoint address="net.tcp://localhost:12345/IWHSMailService"
                    binding="netTcpBinding" bindingConfiguration="NewBinding0"
                    contract="WHSMailCommon.Contracts.IWHSMailService" />
            </service>
        </services>
    </system.serviceModel>
</configuration>

The <services> section defines the services that the WCF host will enable.  This creates a single <service> named WHSMailHost.WHSMailService, which is the full name of the service class that we will implement shortly.  Inside the <service> tag an endpoint is defined.  And endpoint is essentially the "port" on which the client connects.  The endpoint defined here is using the net.tcp protocol and is set to listen on the localhost on port 12345 at the name IWHSMailService.  The next thing defined is a binding.  This sets protocol by which the service will communicate, its configuration, and the contract it implements.  The binding configuration named NewBinding0 (a default name) can be found above inside the <bindings> tag.  The only configuration item that is specified is that security will be turned off for communication between the client and server.  Given that these two machines will be connecting to each other on your own local network, security is not a great concern.  If you were to use this over a real internet connection where the client and server were open to the outside world, you would definitely not want to do this.

There are around several billion other options, protocols, bindings, etc. etc. etc. that can be configured here.  At the end of the article you will find several links to WCF documentation that you can explore to learn more about the WCF internals.  Also note that you can configure WCF with a windows UI by selecting the Service Configuration Editor application installed with the Windows SDK.  You'll find this in the Microsoft Windows SDK program group off your Start menu.

Outlook and MAPI

MAPI (Messaging Application Programming Interface) is what allows one to develop applications and plugins for Microsoft Outlook and other messaging applications which support it (Exchange, Windows Messaging, etc.).  We will be using MAPI to get the data we require from Outlook for our application.

Earlier we created a contract named IWHSMailService.  This contract will be implemented by the WHSMailService class that was also created earlier.  To do this, set a reference to the project's WHSMailCommon assembly.  Then, bring the Contracts and Entities namespaces into the WHSMailService class with the following:

C#

using WHSMailCommon.Contracts;
using WHSMailCommon.Entities;

VB

Imports WHSMailCommon.Contracts
Imports WHSMailCommon.Entities

Then, setup the class to implement the interface as follows:

C#

public class WHSMailService : IWHSMailService

VB

Public Class WHSMailService
    Implements IWHSMailService

Visual Studio will then create the 3 methods that must be implemented according to the interface (GetFolders, GetMessages, GetMessage).  We will fill those in shortly.  But first, we must implement our constructor.  When WCF calls the service, a new instance of the object will be created on each call.  Therefore, it is easy to setup any initialization code for the service in the default constructor method.  In this case, we will initialize the MAPI layer and logon to the default instance.

First, set a reference to the Microsoft Outlook XX.0 Object Library which you will find under the COM tab assuming Outlook is installed as per the instructions above.  Note that the version will depend on what version of Outlook you have installed on your local machine.  I'm using Outlook 2007, so version 12 is what the sample code above is referencing.  If you are using a different version, reference the appropriate version before continuing.

Once the reference is set, bring the namespace into the WHSMailService class with the following line which will import the namespace and setup an alias named Outlook to save some typing:

C#

using Outlook = Microsoft.Office.Interop.Outlook;

VB

Imports Outlook = Microsoft.Office.Interop.Outlook

Now the constructor can be implemented.  We need to get an instance of the Outlook ApplicationClass.  From there we can get an instance of the MAPI namespace.  All methods that we will be using hang off that namespace object.  The code for the constructor follows:

C#

private readonly Outlook.NameSpace _nameSpace = null;

public WHSMailService()
{
    // get an instance of the MAPI namespace and login
    Outlook.Application app = new Outlook.ApplicationClass();
    _nameSpace = app.GetNamespace("MAPI");
    _nameSpace.Logon(null, null, false, false);
}

VB

Private ReadOnly _nameSpace As Outlook.NameSpace = Nothing

Public Sub New()
    ' get an instance of the MAPI namespace and login
    Dim app As Outlook.Application = New Outlook.ApplicationClass()
    _nameSpace = app.GetNamespace("MAPI")
    _nameSpace.Logon(Nothing, Nothing, False, False)
End Sub

With a handle to the namespace, we can write our GetFolders method.  This method will get the default Inbox folder in the default Outlook message store, look at the parent node, recursively enumerate from there, pulling out only folders that contain mail items, and finally sort them alphabetically (recall the CompareTo method of the IComparable interface we implemented earlier).

C#

public List<Folder> GetFolders()
{
    List<Folder> list = new List<Folder>();

    // get the inbox and then go up one level...that *should* be the root of the default store
    Outlook.MAPIFolder root = (Outlook.MAPIFolder)_nameSpace.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderInbox).Parent;

    // add root folder
    Folder folder = new Folder(root.EntryID, root.Name, root.UnReadItemCount, root.Items.Count);
    list.Add(folder);

    // Enumerate the sub-folders
    EnumerateFolders(root.Folders, folder);

    return list;
}

private void EnumerateFolders(Outlook.Folders folders, Folder rootFolder)
{
    foreach(Outlook.MAPIFolder f in folders)
    {
        // ensure it's a folder that contains mail messages (i.e. no contacts, appointments, etc.)
        if(f.DefaultItemType == Outlook.OlItemType.olMailItem)
        {
            if(rootFolder.Folders == null)
                rootFolder.Folders = new List<Folder>();

            // add the current folder and enumerate all sub-folders
            Folder subFolder = new Folder(f.EntryID, f.Name, f.UnReadItemCount, f.Items.Count);
            rootFolder.Folders.Add(subFolder);
            if(f.Folders.Count > 0)
                this.EnumerateFolders(f.Folders, subFolder);
        }
    }

    // alphabetize the list (Folder implements IComparable)
    rootFolder.Folders.Sort();
}

VB

Public Function GetFolders() As List(Of Folder) Implements IWHSMailService.GetFolders
    Dim list As List(Of Folder) = New List(Of Folder)()

    ' get the inbox and then go up one level...that *should* be the root of the default store
    Dim root As Outlook.MAPIFolder = CType(_nameSpace.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderInbox).Parent, Outlook.MAPIFolder)

    ' add root folder
    Dim folder As Folder = New Folder(root.EntryID, root.Name, root.UnReadItemCount, root.Items.Count)
    list.Add(folder)

    ' Enumerate the sub-folders
    EnumerateFolders(root.Folders, folder)

    Return list
End Function

Private Sub EnumerateFolders(ByVal folders As Outlook.Folders, ByVal rootFolder As Folder)
    For Each f As Outlook.MAPIFolder In folders
        ' ensure it's a folder that contains mail messages (i.e. no contacts, appointments, etc.)
        If f.DefaultItemType = Outlook.OlItemType.olMailItem Then
            If rootFolder.Folders Is Nothing Then
                rootFolder.Folders = New List(Of Folder)()
            End If

            ' add the current folder and enumerate all sub-folders
            Dim subFolder As Folder = New Folder(f.EntryID, f.Name, f.UnReadItemCount, f.Items.Count)
            rootFolder.Folders.Add(subFolder)
            If f.Folders.Count > 0 Then
                Me.EnumerateFolders(f.Folders, subFolder)
            End If
        End If
    Next f

    ' alphabetize the list (Folder implements IComparable)
    rootFolder.Folders.Sort()
End Sub

This code will return a generic hierarchal list of our Folder entity objects which will be displayed in the web application.  Note that one of the items assigned to the Folder entity is the EntryID property from the MAPIFolder object.  All MAPI items, be they email messages, folders, appointments, etc. have a unique identifier which is stored in the EntryID field.  We will need this unique value later on to retrieve messages from that folder.

Next, let's implement the GetMessages method.  This method will return a list of messages (minus the bodies) from the folder specified using the GetFolderFromID method, sorted by the received date with the most current first.  It will also handle paging so that the entire folder is not returned at once.

C#

public List<Email> GetMessages(string entryID, int numPerPage, int pageNum)
{
    List<Email> list = new List<Email>();

    Outlook.MAPIFolder f;

    // if no ID specified, open the inbox
    if(string.IsNullOrEmpty(entryID))
        f = _nameSpace.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderInbox);
    else
        f = _nameSpace.GetFolderFromID(entryID, "");

    // to handle the sorting, one needs to cache their own instance of the items object
    Outlook.Items items = f.Items;

    // sort descending by received time
    items.Sort("[ReceivedTime]", true);

    // pull in the correct number of items based on number of items per page and current page number
    for(int i = (numPerPage*pageNum)+1; i <= (numPerPage*pageNum)+numPerPage && i <= items.Count; i++)
    {
        // ensure it's a mail message
        Outlook.MailItem mi = (items[i] as Outlook.MailItem);
        if(mi != null)
            list.Add(new Email(mi.EntryID, mi.SenderEmailAddress, mi.SenderName, mi.Subject, mi.ReceivedTime, mi.Size));
    }

    return list;
}

VB

Public Function GetMessages(ByVal entryID As String, ByVal numPerPage As Integer, ByVal pageNum As Integer) As List(Of Email) Implements IWHSMailService.GetMessages
    Dim list As List(Of Email) = New List(Of Email)()

    Dim f As Outlook.MAPIFolder

    ' if no ID specified, open the inbox
    If String.IsNullOrEmpty(entryID) Then
        f = _nameSpace.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderInbox)
    Else
        f = _nameSpace.GetFolderFromID(entryID, "")
    End If

    ' to handle the sorting, one needs to cache their own instance of the items object
    Dim items As Outlook.Items = f.Items

    ' sort descending by received time
    items.Sort("[ReceivedTime]", True)

    ' pull in the correct number of items based on number of items per page and current page number
    Dim i As Integer = (numPerPage*pageNum)+1
    Do While i <= (numPerPage*pageNum)+numPerPage AndAlso i <= items.Count
        ' ensure it's a mail message
        Dim mi As Outlook.MailItem = (TryCast(items(i), Outlook.MailItem))
        If Not mi Is Nothing Then
            list.Add(New Email(mi.EntryID, mi.SenderEmailAddress, mi.SenderName, mi.Subject, mi.ReceivedTime, mi.Size))
        End If
        i += 1
    Loop

    Return list
End Function

The method takes a string parameter named entryID which is the unique identifier of the folder (from the code above) from which to return the messages.  If no ID is specified, messages are returned from the Inbox.  This code also handles paging by indexing into the array of Items of the MAPIFolder object at the specified position and counting out numPerPage records to be returned.  A list of Email entity objects is returned from this method to be displayed on the web interface.

Finally, let's implement the GetMessage method.  This will return a specific message based on the MAPI EntryID using the GetItemFromID method and format the body for plain text or HTML display.

C#

public Email GetMessage(string entryID)
{
    // pull the message
    Outlook.MailItem mi = (_nameSpace.GetItemFromID(entryID, "") as Outlook.MailItem);

    if (mi != null)
    {
        string body;

        // if it's a plain format message, wrap it in <pre> tags for nice output
        if(mi.BodyFormat == Outlook.OlBodyFormat.olFormatPlain)
            body = "<pre>" + mi.Body + "</pre>";
        else
            body = mi.HTMLBody;

        return new Email(mi.EntryID, mi.SenderEmailAddress, mi.SenderName, mi.Subject, mi.ReceivedTime, mi.Size, body);
    }
    else
        return null;
}

VB

Public Function GetMessage(ByVal entryID As String) As Email Implements IWHSMailService.GetMessage
    ' pull the message
    Dim mi As Outlook.MailItem = (TryCast(_nameSpace.GetItemFromID(entryID, ""), Outlook.MailItem))

    If Not mi Is Nothing Then
        Dim body As String

        ' if it's a plain format message, wrap it in <pre> tags for nice output
        If mi.BodyFormat = Outlook.OlBodyFormat.olFormatPlain Then
            body = "<pre>" & mi.Body & "</pre>"
        Else
            body = mi.HTMLBody
        End If

        Return New Email(mi.EntryID, mi.SenderEmailAddress, mi.SenderName, mi.Subject, mi.ReceivedTime, mi.Size, body)
    Else
        Return Nothing
    End If
End Function

With these three methods in place, we can get our folders, get messages in those folders, and get a specific message from a folder.  Now we can switch our attention to the web-based "client" application which will plug into WHS.

 

ASP.NET "Client"

The client we are going to produce is going to be a very simple 3-paned screen much like Outlook with folders on the left, messages on the top right, and message text on the bottom right.

Create a new ASP.NET Web Application named WHSMailWeb (if you are using the Express editions of Visual Studio, you will need to do this in Visual Web Developer Express).  Set a reference to the WHSMailCommon assembly along with the System.ServiceModel assembly.  Again, if you are in Express, you will have to set the reference to WHSMailCommon in the other project's bin directory.

First, let's configure the ASP.NET application so it can properly call our WCF service running on the host machine.  Open the web.config file and add the following after the end </system.web> tag and before the end </configuration> tab:

<system.serviceModel>
    <bindings>
        <netTcpBinding>
            <binding name="NewBinding0" maxReceivedMessageSize="1048576">
                <readerQuotas maxStringContentLength="1048576" />
                <security mode="None" />
            </binding>
        </netTcpBinding>
    </bindings>
    <client>
        <endpoint address="net.tcp://SERVERNAME:12345/IWHSMailService"
            binding="netTcpBinding" bindingConfiguration="NewBinding0"
            contract="WHSMailCommon.Contracts.IWHSMailService" name="WHSMailService" />
    </client>
</system.serviceModel>

This looks very similar to the configuration information from our host service.  An endpoint is defined which points to the host server.  YOU WILL NEED TO CHANGE THE "SERVERNAME" HOST TO THE NAME OR IP ADDRESS OF THE MACHINE THAT WILL RUN THE HOST SERVICE WE CREATED ABOVE!

You will see the same setup for the binding and contract.  The name parameter will be used by the code a bit later so WCF knows how to contact the host to instantiate the remote object.

The one thing that is different here is the netTcpBinding configuration.  The security mode is set to none as it was before, but we have the addition of the maxReceivedMessageSize and maxStringContentLength.  Both of these are set at 1 megabyte to allow that much data to be transferred from the host to the client.  The default is only 64K which is not enough to return most of the data we need to serialize and transmit between the two machines.

Next, add a page to the project named BasePage.  This page will be inherited by all pages in the project to easily startup and shutdown the WCF channel required to retrieve the message data.

Open the code-behind file for BasePage and add the following code:

C#

using System;
using System.ServiceModel;
using WHSMailCommon.Contracts;

namespace WHSMailWeb
{
    public partial class BasePage : System.Web.UI.Page
    {
        ChannelFactory<IWHSMailService> _factory = null;
        private IWHSMailService _channel = null;

        protected void Page_Init(object sender, EventArgs e)
        {
            // create a channel factory and then instantiate a proxy channel
            _factory = new ChannelFactory<IWHSMailService>("WHSMailService");
            _channel = _factory.CreateChannel();
        }

        protected void Page_Unload(object sender, EventArgs e)
        {
            try
            {
                _factory.Close();
            }
            catch
            {
                // for the moment, we don't really care what happens here...if it fails, so be it
            }
        }

        public IWHSMailService WHSMailService
        {
            get { return _channel; }
        }
    
    }
}

VB

Imports Microsoft.VisualBasic
Imports System
Imports System.ServiceModel
Imports WHSMailCommon.Contracts

Namespace WHSMailWeb
    Public Partial Class BasePage
        Inherits System.Web.UI.Page
        Private _factory As ChannelFactory(Of IWHSMailService) = Nothing
        Private _channel As IWHSMailService = Nothing

        Protected Sub Page_Init(ByVal sender As Object, ByVal e As EventArgs)
            ' create a channel factory and then instantiate a proxy channel
            _factory = New ChannelFactory(Of IWHSMailService)("WHSMailService")
            _channel = _factory.CreateChannel()
        End Sub

        Protected Sub Page_Unload(ByVal sender As Object, ByVal e As EventArgs)
            Try
                _factory.Close()
            Catch
                ' for the moment, we don't really care what happens here...if it fails, so be it
            End Try
        End Sub

        Public ReadOnly Property WHSMailService() As IWHSMailService
            Get
                Return _channel
            End Get
        End Property

    End Class
End Namespace

This code overrides the page's Init and Unload methods.  Page_Init is called at the beginning of a page request and Page_Unload is called just before the request completes.

The Page_Init method here uses WCF to create a ChannelFactory object factory that will return proxy objects of type IWHSMailService.  Recall that this is the interface which defines the contract of our service.  The name passed as a parameter to the ChannelFactory constructor must match the name of the service in the configuration file, as discussed earlier.

Next, a channel object is created by calling CreateChannel from the ChannelFactory.  This is what returns the fake, proxy object defined by our contract.  A property named WHSMailService at the bottom of the class exposes this so that the inherited pages can easily use the object to call the host service.  We'll see this a bit later.

The Page_Unload method simply closes the factory object and very poorly handles any exceptions that may occur when doing so.  An error on close isn't critical to this application, but that is certainly not always the case.

Now we will implement the default page.  I'm not going to repeat the entire HTML of the page itself here, and I'm certainly not a HTML/CSS designer, so I wouldn't recommend learning from that anyhow.  It does, however, get the job done.

What you do need to know is that the page contains 4 <DIV> tags:  one contains an ASP.NET TreeView object (the left-most pane), one contains an ASP.NET GridView object (the top-right pane), one contains 2 ASP.NET LinkButton's which implement a Next/Back paging scheme (the middle-right pane),  and the last contains a table to display some header information, and a <DIV> to display the message contents.

After that poor description, here's what the application looks like in an instance of Internet Explorer with my instance of Outlook (with some blurring over the (not really) sensitive data):

webmail

Now let's implement the actual logic for the page.  Open the Default.aspx page's code behind file.  Bring in the WHSMailCommon.Entities namespace as follows:

C#

using WHSMailCommon.Entities;

VB

Imports WHSMailCommon.Entities

Next, change the _Default class to inherit from BasePage instead of System.Web.UI.Page:

C#

public partial class _Default : BasePage

VB

Public Partial Class _Default
    Inherits BasePage

Then, implement the Page_Load method.  This method will be called after the Page_Init method which is implemented in our parent object and will be called automatically.

C#

private string _folderEntryID = string.Empty;
private int _pageNum = 0;

protected void Page_Load(object sender, EventArgs e)
{
    // first time in (tvFolders has viewstate enabled
    if(tvFolders.Nodes.Count == 0)
    {
        // get the folder tree
        List<Folder> folderList = this.WHSMailService.GetFolders();

        // add the root node and expand it
        TreeNode node = new TreeNode(folderList[0].Name, folderList[0].EntryID);
        node.Expanded = true;
        tvFolders.Nodes.Add(node);

        // load up the sub-folders
        LoadNode(folderList[0].Folders, node);

        // get the inbox list and bind it to the grid
        List<Email> list = GetMessages();
        BindMessages(list);

        // save off the default page number and folder ID
        ViewState["PageNum"] = _pageNum;
        ViewState["FolderID"] = _folderEntryID;
    }

    _pageNum = int.Parse(ViewState["PageNum"].ToString());
    _folderEntryID = ViewState["FolderID"].ToString();
}

private void LoadNode(List<Folder> folders, TreeNode node)
{
    foreach(Folder f in folders)
    {
        // add the node with the format of Folder (Unread/Total)
        TreeNode subNode = new TreeNode(f.Name + " (" + f.UnreadMessages + "/" + f.TotalMessages + ")", f.EntryID);

        // expand and select the inbox
        subNode.Expanded = subNode.Selected = (f.Name == "Inbox");
        node.ChildNodes.Add(subNode);

        // load the subfolders
        if(f.Folders != null)
            LoadNode(f.Folders, subNode);
    }
}

private List<Email> GetMessages()
{
    // get a group of messages based on the current page number and size
    return this.WHSMailService.GetMessages(_folderEntryID, GridView1.PageSize, _pageNum);
}

private void BindMessages(List<Email> list)
{
    // load the grid
    GridView1.DataSource = list;
    GridView1.DataBind();

    // save off the new page number
    ViewState["PageNum"] = _pageNum;
}

VB

Private _folderEntryID As String = String.Empty
Private _pageNum As Integer = 0

Protected Sub Page_Load(ByVal sender As Object, ByVal e As EventArgs)
    ' first time in (tvFolders has viewstate enabled
    If tvFolders.Nodes.Count = 0 Then
        ' get the folder tree
        Dim folderList As List(Of Folder) = Me.WHSMailService.GetFolders()

        ' add the root node and expand it
        Dim node As TreeNode = New TreeNode(folderList(0).Name, folderList(0).EntryID)
        node.Expanded = True
        tvFolders.Nodes.Add(node)

        ' load up the sub-folders
        LoadNode(folderList(0).Folders, node)

        ' get the inbox list and bind it to the grid
        Dim list As List(Of Email) = GetMessages()
        BindMessages(list)

        ' save off the default page number and folder ID
        ViewState("PageNum") = _pageNum
        ViewState("FolderID") = _folderEntryID
    End If

    _pageNum = Integer.Parse(ViewState("PageNum").ToString())
    _folderEntryID = ViewState("FolderID").ToString()
End Sub

Private Sub LoadNode(ByVal folders As List(Of Folder), ByVal node As TreeNode)
    For Each f As Folder In folders
        ' add the node with the format of Folder (Unread/Total)
        Dim subNode As TreeNode = New TreeNode(f.Name & " (" & f.UnreadMessages & "/" & f.TotalMessages & ")", f.EntryID)

        ' expand and select the inbox
        subNode.Selected = (f.Name = "Inbox")
        subNode.Expanded = subNode.Selected
        node.ChildNodes.Add(subNode)

        ' load the subfolders
        If Not f.Folders Is Nothing Then
            LoadNode(f.Folders, subNode)
        End If
    Next f
End Sub

Private Function GetMessages() As List(Of Email)
    ' get a group of messages based on the current page number and size
    Return Me.WHSMailService.GetMessages(_folderEntryID, GridView1.PageSize, _pageNum)
End Function

Private Sub BindMessages(ByVal list As List(Of Email))
    ' load the grid
    GridView1.DataSource = list
    GridView1.DataBind()

    ' save off the new page number
    ViewState("PageNum") = _pageNum
End Sub

If the tvFolders tree-view has not been filled in, we call our host service's GetFolders method from the WHSMailService property as defined above.  The method then enumerates through the hierarchical list of folders returned, building the same tree structure that exists in Outlook.  The Text property of each TreeNode is set to the folder name with a count of messages unread and total count of messages.  The Value property of the node is set to the unique MAPI-defined EntryID so that we can later grab that value to return the list of messages from that specific folder.

Next, we call our service's GetMessages method to return messages from the Inbox.  We pass in the value from the grid view's PageSize property and a member variable named _pageNum.  This will handle our paging scheme as we discussed earlier.  Once that list of messages is returned, is it bound to the data grid for display.

Finally, the default page number and folder entry ID (0 and "" respectively) are stored in the ViewState so they can be assigned back to our member variables on each page request.

If you were to run the application right now, you would see the same page that you do above, minus the message text at the bottom.

Next, we will implement what occurs when a user clicks on a folder in the tree view.  We simply pull out the MAPI unique EntryID which is stored in the Value field of the selected node, assign it to the local member variable and view state, and then call the GetMessages method from our service to return a list of messages from that folder, just as we did with the inbox above.  The code below shows the process:

C#

protected void tvFolders_SelectedNodeChanged(object sender, EventArgs e)
{
    // new folder, so reset the view
    _pageNum = 0;
    _folderEntryID = this.tvFolders.SelectedNode.Value;
    ViewState["FolderID"] = _folderEntryID;

    List<Email> list = GetMessages();
    BindMessages(list);
}

VB

Protected Sub tvFolders_SelectedNodeChanged(ByVal sender As Object, ByVal e As EventArgs)
    ' new folder, so reset the view
    _pageNum = 0
    _folderEntryID = Me.tvFolders.SelectedNode.Value
    ViewState("FolderID") = _folderEntryID

    Dim list As List(Of Email) = GetMessages()
    BindMessages(list)
End Sub

Next, we can implement the Next/Back link buttons.  These methods simply increment or decrement the current page number, assign it to the view state for use on the next request, and then, just as before, gets the list of messages specific to the currently selected folder, using the unique ID stored in the view state.

C#

protected void btnPrev_Click(object sender, EventArgs e)
{
    // first page
    if(_pageNum > 0)
        _pageNum--;

    List<Email> list = GetMessages();
    BindMessages(list);
}

protected void btnNext_Click(object sender, EventArgs e)
{
    // next page
    _pageNum++;
    List<Email> list = GetMessages();

    // if we're out of messages, go back to the previous page
    if(list == null || list.Count == 0)
    {
        _pageNum--;
        list = GetMessages();
    }
    BindMessages(list);
}

VB

Protected Sub btnPrev_Click(ByVal sender As Object, ByVal e As EventArgs)
    ' first page
    If _pageNum > 0 Then
        _pageNum -= 1
    End If

    Dim list As List(Of Email) = GetMessages()
    BindMessages(list)
End Sub

Protected Sub btnNext_Click(ByVal sender As Object, ByVal e As EventArgs)
    ' next page
    _pageNum += 1
    Dim list As List(Of Email) = GetMessages()

    ' if we're out of messages, go back to the previous page
    If list Is Nothing OrElse list.Count = 0 Then
        _pageNum -= 1
        list = GetMessages()
    End If
    BindMessages(list)
End Sub

And finally, we need to implement what happens when the user selects a message from the top pane.  If you look at the HTML for the Default page, you will see that the Subject field is bound to a LinkButton control in the grid view via a TemplateField (the rest of the fields are standard BoundFields.  The LinkButton sets the CommandArgument property to the unique EntryID of that message as defined by MAPI.  So, all we need to do is listen on the server for the LinkButton's Command event, grab the unique ID and call our service's GetMessage method with that ID to return the message itself. 

C#

protected void btnLink_Command(object sender, CommandEventArgs e)
{
    Email email = this.WHSMailService.GetMessage(e.CommandArgument.ToString());

    // fill out the header with some basic info
    lblFrom.Text = email.FromName + "&nbsp;(" + email.From + ")";
    lblSubject.Text = email.Subject;
    lblReceived.Text = email.Received.ToString();

    // when a message is selected, write out the content
    msgContent.InnerHtml = email.Body;
}

VB

Protected Sub btnLink_Command(ByVal sender As Object, ByVal e As CommandEventArgs)
    Dim email As Email = Me.WHSMailService.GetMessage(e.CommandArgument.ToString())

    ' fill out the header with some basic info
    lblFrom.Text = email.FromName & "&nbsp;(" & email.From & ")"
    lblSubject.Text = email.Subject
    lblReceived.Text = email.Received.ToString()

    ' when a message is selected, write out the content
    msgContent.InnerHtml = email.Body
End Sub

The Email entity is retrieved, its properties are set to the labels in the header, and the message content is assigned to the InnerHtml property of the content holding <DIV>.

Voila!  A very simple mail reader.

That's it for code.  Now we need to deploy the applications and configure Windows Home Server.

 

Deployment

WHSMailHost

If you are deploying to a machine that is not your development machine, install the .NET Framework 3.0 runtime on the machine to which you will be running the host service.  Then, copy the WHSMailHost.exe, WHSMailHost.exe.config, and WHSMailCommon.dll files to the machine running Outlook that will be connected to by the web application.  You may want to create a shortcut in the Startup group so it will launch when you log into Windows.

Next, open Outlook and select Trust Center from the Tools menu.  Choose Programmatic Access and set the option to Never warn me about suspicious activity.  Unfortunately this must be disabled, otherwise Outlook will prompt you every time the host process attempts to retrieve any data.  There is no way to bypass this on an application-by-application basis, so we have to turn it off for all applications.

trustcenter

If you are running firewall software on the PC, ensure that port 12345 will allow traffic to pass through.  You may also change port 12345 to a different port, but you must change it in the configuration files for both applications.

Finally, note that Outlook does not need to remain open to use this application. You do, however, need to be logged into the machine with the host running.

WHSMailWeb

On your Windows Home Server machine, install the .NET Framework 3.0 Runtime.  This is needed to use WCF.  Next, create a new directory named mail (or anything you wish, but it is assumed it is named mail) in the c:\inetpub directory.  Copy all .aspx pages, web.config, icon.png and the bin directory to the mail directory.  You could also share out the mail directory and use Visual Studio's Publish command to automatically copy the files to that share.  Then, open the Internet Information Services (IIS) Manager application from the Administrative Tools group on the Start menu.  Open Web Sites and then Default Web Site in the left pane.  Right-click on Default Web Site and select New -> Virtual Directory... from the context menu.  When prompted, use the following values:

Alias mail
Path c:\inetpub\mail
Permissions Read, Run Scripts

The next part is a bit tricky.  We want to integrate with the security already provided by Windows Home Server.  The WHS remote website uses Forms security from ASP.NET.  Ideally, you should be able to log into the main page of the WHS remote website and then open the webmail link without having to log in again.  Additionally, if the mail URL is used directly, you should be prompted for your login credentials.

To achieve this, open the web.config file from c:\inetpub\remote in Notepad.  In the XML, between the start and end <system.web> tags you will find the configuration for the Forms-based authentication used by WHS.  In order to use the cookie that is created by this authentication method, we need to have the same key values in our web.config file.  The easiest way to achieve this is to copy all of the the text between the two <system.web> tags.  Next, open the web.config file from the mail directory that you just copied over and paste the text between the two lines informing you to do so.  Finally, we need to update the loginUrl and defaultRedirect paths in the forms and customErrors keys.  Add /remote/ to the beginning of each to make them /remote/login.aspx and /remote/error.aspx.  Be sure that the SERVERNAME item in the endpoint configuration is updated to the name/IP address of the machine running the host process.

With the files copied and edited, ensure that permissions on the file are propagated from the root mail directory to the files inside.

Finally, open the websites.xml file in the /remote directory and add the following XML before the end </WebSites> tag:

<WebSite name="Outlook Webmail" uri="/mail" imageUrl="/mail/icon.png" absolute="false"></WebSite>

With the configuration done, we need to enable web site connectivity in WHS.  Double-click the Windows Home Server Console icon on the desktop.  When it loads, click the Settings button in the top right corner.  When the Settings dialog appears, select Remote Access in the left pane and then click the Turn On button in the right pane, assuming web site connectivity is off.  If it already enabled, skip this step.

whssettings

With all that done, create a user account for yourself in WHS so that you can login remotely.  That's it!

Running the Application

Ensure the WHSMailHost application is running on the PC with Outlook installed.  Then, browse to your Windows Home Server's default website.  After logging in, you should see a link on the right side of the screen named Outlook Webmail

web

Click that to start our application and if everything is working, you should see the web page as shown above.

Troubleshooting

If the web page displays an error message, open the web.config file in the mail directory on your Windows Home Server.  Look for the <customErrors> tag and change the mode parameter to Off.  This should allow you to see the actual error that is occurring and provide more information as to the status of the application.

Conclusion

Phew!  Two applications and one shared assembly later, we have a web-based add-in for Windows Home Server that allows one to remotely access their Outlook message store.  Currently the application is read-only and provides only minimal functionality.  It's a great starting point to create a full-fledged webmail client interface.  The project is hosted on CodePlex so I'm hoping to see you readers implement additional features and functionality that you find useful.

Additional Information

Here are a few links with additional information on the items discussed here:

Bio

Brian is a Microsoft C# MVP and a recognized .NET expert with over 6 years experience developing .NET solutions, and over 9 years of professional experience architecting and developing solutions using Microsoft technologies and platforms, although he has been "coding for fun" for as long as he can remember.  Outside the world of .NET and business applications, Brian enjoys developing both hardware and software projects in the areas of gaming, robotics, and whatever else strikes his fancy for the next ten minutes. He rarely passes up an opportunity to dive into a C/C++ or assembly language project.  You can reach Brian via his blog at http://www.brianpeek.com/.

Tags:

Follow the Discussion

  • markvadermarkvader

    You stated at the start of the article, that the project would work fine on XP, is this the case for home edition as well.

  • markvadermarkvader

    Thanks Brian, great idea for a project.

    I'm struggling to get it to work though, I have everything deployed (using your code rather than coding myself at this stage) but i continue to get the message.

    "No connection could be made because the target machine actively refused it"

    I have ensured that the firewall is open to port 12345

    I can see norton listening to TCP port 12345.

    Can anyone suggest what might be stopping this from working.  

    thanks

    Mark

  • Noticias externasNoticias externas

    In case you missed it, Brian Peek created a Windows Home Server add-in for that lets people view their

  • Clint RutkasClint I'm a "developer"

    In case you missed it, Brian Peek created a Windows Home Server add-in for that lets people view their

  • Richard KnightRichard Knight

    Just a quick not for Vista Users.

    You must run outlook as an administrator to be able to change the security setting. Once the setting is changed, you can go back to running it normally.

  • BlackmoonBlackmoon

    Very good solution, easy to understand. In this way learning and understanding Win Servers is fine!

    Greetz from http://www.techdummie.de

  • PromiPromi

    Thanks for this very helpful and interesting post.

  • MeissenMeissen

    This is a great article, thanks!

  • MarkMark

    [FaultException: The server was unable to process the request due to an internal error.  For more information about the error, either turn on IncludeExceptionDetailInFaults (either from ServiceBehaviorAttribute or from the <serviceDebug> configuration behavior) on the server in order to send the exception information back to the client, or turn on tracing as per the Microsoft .NET Framework 3.0 SDK documentation and inspect the server trace logs.]

      System.Runtime.Remoting.Proxies.RealProxy.HandleReturnMessage(IMessage reqMsg, IMessage retMsg) +2668969

      System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(MessageData& msgData, Int32 type) +717

      WHSMailCommon.Contracts.IWHSMailService.GetFolders() +0

      WHSMailWeb._Default.Page_Load(Object sender, EventArgs e) in C:\Projects\Personal\WHSMail\WHSMailWeb\Default.aspx.cs:19

      System.Web.Util.CalliHelper.EventArgFunctionCaller(IntPtr fp, Object o, Object t, EventArgs e) +15

      System.Web.Util.CalliEventHandlerDelegateProxy.Callback(Object sender, EventArgs e) +33

      System.Web.UI.Control.OnLoad(EventArgs e) +99

      System.Web.UI.Control.LoadRecursive() +47

      System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint) +1436

  • Clint RutkasClint I'm a "developer"

    @Mark, please contact me directly through my blog at:

    http://www.brianpeek.com/blog/contact.aspx

    That will get an email direct to me (Brian) and we can figure out what's going on.

  • dj oceandj ocean

    it all works perfectly. to bad it still has limited functionality. I was hoping It would look more like exchange.

    is there already someone out there who created additional functionality ? i would shurely love to use it

  • LarsLars

    really interesting, thank you!

  • kiteboardertjekiteboarder​tje

    Is it also possible to creat pushmail on home server?

    great article by the way

  • übersetzungenübersetzung​en

    There are many useful informations in this great article…I really enjoy reading the whole blog that you write...Thanks!

  • GewinnspieleGewinnspiele

    great article.

    @Richard Knight: thanks for the tip.

  • GothicGothic

    A truly wonderful article. Thanks to the author

  • StodgeStodge

    Are there any example out there of how to perform the WHS installation via an MSI, with all the web.config tweaks etc performed automatically?

  • SteveSteve

    I keep getting a refused connection to port 808.

    This is confusing because the active port is 12345.

  • RoyRoy

    Hi all,

    It runs fine here but other users at my WHS who have acces to remote login can also connect to the mailbox??

    Is there any posebillity to avoid that?

  • SCSC

    Could you not just create a download version of a working version, instead of having to create it yourself.

    I know the point is that your coding for fun, but....

Remove this comment

Remove this thread

close

Comments Closed

Comments have been closed since this content was published more than 30 days ago, but if you'd like to continue the conversation, please create a new thread in our Forums,
or Contact Us and let us know.