Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

A MIME Message Viewing Library in WPF

0.00/5 (No votes)
31 Jul 2012 1  
The source code and a simple demo program for a plug-in in Microsoft Managed Extensibility Framework (MEF) that provides visual display of email messages.

MIME message presenter

1. Introduction

Electronic mail (email) are one of the most commonly used means to exchange and keep private information. Although a user's emails can be read online using web interface or offline using many free email clients, there are still times when one would like to have an email reader that can be integrated into his/her own systems because of various reasons. For example, one may need an email display plug-in for a WPF project that is free and provide downloading, sorting, searching, safekeeping, and reading a large amount of email messages, many of which are still not provided by any of the online and offline solutions available to public. In addition, it has to parse and display most messages that a user can encounter. After some investigation and searching on the internet, it can be concluded that nothing that suit the need can be found. It is time to develop one on our own.

The current package contains the source code and a simple demo program for a plug-in in Microsoft Managed Extensibility Framework (MEF) that provides visual display of email messages. It is ready to be incorporated into any WPF based application. Since this is an initial version, emphasis was on the completeness of information representation. The visuals and ease of use aspects will be improved in later versions of it, therefore it is not about WPF visual effects.

2. Background

Most of the email messages are composed using the Multipurpose Internet Mail Extensions (MIME) format. It breaks the original ASCII text based messaging limitations to transmit, via SMTP, non-ASCII characters, binary data and alternative views of a message in a single ASCII text based data block. These messages also most likely contains a HTML formatted view part for a better presentation quality, media embedding and possibility to link to other sources.

The text based MIME message represents a tree, each of its node contains a particular part of the message . The node type and ID is used to declare relationship between those parts of the message. It needs to be parsed to generate a MIME entity before presenting. Modern email clients can generate rather complex multimedia MIME messages with different encodings. A good parser that fully support MIME format is needed. After a few modifications and adaptations, the corresponding MIMIE parser provide by Ivar Lumi (click here) was extracted from his much larger library. The resulting dll is included inside the download package.

HTML formatted messages can be displayed natively using WPF controls (namely, inside a FlowDocument object). But because of the complexity, to fully recreate the same effect as they are displayed in a browser does require lengthy work, too much to be justified for our purposes. They are therefore to be displayed in a web browser hosted inside a WPF control. Web browsers do not accept in memory data as input, the data must either be send via a network socket or using a local file. Using local file has a few risks:

  • It fills disk space by temporary data and potentially too much fragment in it.
  • It exposes more security weaknesses and causes privacy leak concerns.
  • It does not taste that good.
  • etc.

It was decided to use a embedded HTTP server to send the in-memory data over to the browser to display.

A full fledged HTTP server can be very complicated to develop because it has to implement full HTTP protocol, handle scalability, extensibility, concurrency, cache management, etc..  But for our single user and embedded usage context, what is needed is just a partial implementation of HTTP protocol, turn the cache off, and ignore all other concerns. It is in fact rather simple to develop, especially when there is even a HttpListener class in .NET 4.0.

3. The Entry Point, Event, Delegate

Let's introduce the interface using which a host that can interact with the library. A UI library exposes not only the API, but also events and callbacks (delegates). It is to this end that the current section is supposed to be read.

The entry point for the current library is a message body display panel supported by a type called EMailPresenter. It implements IEMailReader.

public interface IMessageReader
{
    Uri SourceUri { get; set; }
}

public delegate bool QueryMessageThreadHanlder(string MsgID, out ThreadedMessage StartMsg);

public interface IEMailReader : IMessageReader
{
    QueryMessageThreadHanlder QueryThread
    {
        get;
        set;
    }
}

which is defined in a separated lib called IFileSystemVShell, which should be shared by the host and the current library. Shown below is the top part of the EMailPresenter class definition that implement the interface:

namespace CryptoGateway.Documents.Mime
{
    [Export(typeof(IEMailReader))]
    public partial class EMailPresenter : UserControl, IEMailReader
    {
        public Uri SourceUri
        {
            get { return (Uri)GetValue(SourceUriProperty); }
            set
            {
                if (SourceUri == null && value != null || SourceUri != null && value == null
       				|| SourceUri.ToString() != value.ToString())
                    SetValue(SourceUriProperty, value);
                if (prev_uri == value.ToString())
                    return;
                prev_uri = value.ToString();
                if (IsAlreadyExpanded || !IsExpandedViewOpened)
                {
                    if (IsAlreadyExpanded)
                    {
                        if (!ThreadSelection && RootModel.ThreadViewer != null 
                                 && !RootModel.ThreadViewer.IsInThread(value.LocalPath))
                            RefreshThreadPending = true;
                    }
                    DisplayMsg();
                }
                else
                    RootModel.ReaderWindow.SourceUri = value;
            }
        }
        public static readonly DependencyProperty SourceUriProperty =
            DependencyProperty.Register("SourceUri", typeof(Uri), typeof(EMailPresenter), 
			new UIPropertyMetadata(null, (o, e) =>
            {
                (o as EMailPresenter).SourceUri = e.NewValue as Uri;
            }));

        ...
        ...
        ...
        ...
        
    }
}
  • The attribute "[Export(typeof(IEMailReader))]" exports the control as a plug-in inside the MEF framework.
  • Property "SourceUri" implements the corresponding interface declared inside IEMailReader. The host can simply set a valid Uri for it to have the embedded HTTP server to send intended data to the browser to be displayed. Only when SourceUri refers to a (MIME message) file on a user's local file system is handled in the current version of the library. A user can easily extend it so it can load from other sources, like from a HTTP or FTP server. The extension can be done by modifying a single method, namely:
  • private void DisplayMsg()
    {
        if (SourceUri == null)
        {
            ClearView();
            return;
        }
        if (File.Exists(SourceUri.LocalPath))
        {
            StreamReader sr = new StreamReader(SourceUri.LocalPath);
            Data.RawText = sr.ReadToEnd();
            sr.Close();
            byte[] bf = Encoding.ASCII.GetBytes(Data.RawText);
            DisplayMsg(bf);
        }
        else
            ClearView();
    }

    of EMailPresenter class. This is the single point inside the library where an MIME message is loaded before been parsed to generate an MIME entity (tree).

  • The Event TransCompSelectFolderEvent is raised inside the current library when it needs to ask a user to select a folder. For example, inside the EMailAttachments control which handles MIME attachments. Instead of providing a folder selection dialog itself, the library raises an event to let the host to decide. This is because WPF does not has a folder selection dialog built in, so an custom folder selection dialog has to be used, like the one provided by WinForms. Making the current library depending on an extra custom library, like WinForms, will cause it to depend on something it need not to. So it is delegated to the hosting environment in which a user could have other preferred folder selector than the WinForms one, like in our FileSystem SQLizer System. A user who like to handle the event should capture it somewhere inside the visual tree containing the plug-in.
  • QueryMessageThreadHanlder delegate. The library is supposed to display a single message provided by the host. It has no idea about the set of messages the current displaying message belongs to. This delegate is used to query the host, which hold a complete set of MIME messages, for message threading information.

4. WPF Controls

The user control EMailPresenter has multiple appearances, depending on the particular message under viewing and on whether the display is in brief or detailed mode. Besides some control buttons, it only contains a ContentPresenter control, which is late-binded to a DataTemplate selected from the resource library defined inside it Resources property:

<UserControl x:Class="CryptoGateway.Documents.Mime.EMailPresenter"
           xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
           xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
           xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
           xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
           xmlns:loc="clr-namespace:CryptoGateway.Documents.Mime"
           xmlns:res="clr-namespace:CryptoGateway.Documents.Mime.Properties"
           xmlns:avalonedit="http://icsharpcode.net/sharpdevelop/avalonedit"
           mc:Ignorable="d" 
           d:DesignHeight="300" d:DesignWidth="300" Loaded="OnLoaded" 
           Initialized="OnInitialized">
<UserControl.Resources>
    <ResourceDictionary>
        <ResourceDictionary.MergedDictionaries>
            <ResourceDictionary Source="Resources/ControlStyles.xaml" />
    </ResourceDictionary.MergedDictionaries>
    <DataTemplate x:Key="A">
        <TabControl>
            <TabItem Header="{Binding TextHeaderStr}">
                 <TextBox Text="{Binding PlainText}" IsReadOnly="True" Background="White" 
                     TextWrapping="Wrap" 
                     VerticalScrollBarVisibility="Auto" />
            </TabItem>
            <TabItem Header="{Binding RawHeaderStr}">
                 <ScrollViewer VerticalScrollBarVisibility="Auto" 
                             HorizontalScrollBarVisibility="Disabled" 
                             VerticalAlignment="Top" >
                     <loc:LargeTextBox Text="{Binding RawText}" />
                 </ScrollViewer>
            </TabItem>
            <TabItem Header="{Binding OtherAspectsHeaderStr}">
                 <ScrollViewer VerticalScrollBarVisibility="Auto" 
                             HorizontalScrollBarVisibility="Auto" 
                             VerticalAlignment="Top" >
                     <loc:EMailMetaDetails DataContext="{Binding EntityRoot}"/>
                 </ScrollViewer>
            </TabItem>
        </TabControl>
    </DataTemplate>
    <DataTemplate x:Key="AD">
        <TabControl>
            <TabItem Header="{Binding TextHeaderStr}">
               ....
            </TabItem>
            ...
        </TabControl>
    </DataTemplate>
</UserControl.Resources>   
    ...   
</UserControl>

where each appearance is identified by the resource Key ("A", "AD", ...) above. The library select the right one for each message that is going to be displayed. The display contains tab pages, in the most complete display, the following views about the MIME message are displayed:

Figure: Correlated message threading

Dog on fire

Figure: Attachment preview
  1. Plain text view, if available.
  2. HTML view if available.
  3. Raw data view. It contains the raw message data for a knowledgeable or inquisitive user to inspect.
  4. Others, which contains a trace information about sending agent, attachments, etc., contained inside the message. This is one of the extension point where future release of the library will add more information into it.

EMailPresenter, which is an embedded user control for brief viewing, opens the full reader EMailReader. It contains:

  1. EMailThreadView is a control that correlates MIME messages in an conversation process between a user and one of his/her peer. Given an arbitrary message in a related set of messages, it displays the threading of MIME messages in a tree view that represents which message a particular message is replying to and the set of messages that replied that message. Such a functionality is quite often seen in a public forum, but is lacking in any email clients known to the author. Gmail can display such correlation, but in a flat fashion that confuses at least the author. In even a two person conversation, such a tree can be very complex but is hiding from a user so far.
  2. QueryMessageThreadHanlder is used to query the host for the initial message and the entire correlation tree information (see the figure on the right) carried by an object of type ThreadedMessage, given the message id of the current message. The implementation of the handler involves recursive traversal of the set of message that is related to the current message. It will not be discussed further here.

  3. Message header information area where the basic MIME message header information is displayed.
  4. EMailPresenter which displays the body of the message.

EMailPresenter contains the EMailMetaDetails, EMailAttachments, and LargeTextBox user controls.

  1. EMailAttachments contains a list view on top and a multimedia preview panel at the bottom. Current types of multimedia data that can be previewed are only those for image data varieties. It emits a TransCompSelectFolderEvent event when a user tries to save one of the attachments, which has to be handled by the host to answer the question: "where to save the data?".
  2. private void OnSaveAttachment(object sender, RoutedEventArgs e)
    {
        Button btn = e.OriginalSource as Button;
        if (!(btn.DataContext is MimeWrapper))
            return;
        MimeEntity de = (btn.DataContext as MimeWrapper).Entity;
        if (de != null)
        {
            TransCompSelectFolderEventArgs ex = new TransCompSelectFolderEventArgs(
                                     ComponentEvents.TransCompSelectFolderEvent);
            ex.EventTitle = Properties.Resources.AttachmentSaveDirSelWord;
            ex.InitialFolderPath = LastDepositFolder;
            RaiseEvent(ex);
            if (ex.Handled && ex.IsSelectionMade && 
                                       !string.IsNullOrEmpty(ex.SelectedFolderPath))
            {
                if (Directory.Exists(ex.SelectedFolderPath))
                {
                    LastDepositFolder = ex.SelectedFolderPath;
                    SaveFile(ex.SelectedFolderPath, 
                                             de.ContentDisposition_FileName, de);
                }
                else if (ex.SelectedFolderPath.LastIndexOf('\\') != -1)
                {
                    string dir = ex.SelectedFolderPath.Substring(0, 
                                     ex.SelectedFolderPath.LastIndexOf('\\') + 1);
                    if (Directory.Exists(dir))
                        SaveFile(dir,
                                  ex.SelectedFolderPath.Substring(dir.Length), de);
                }
            }
        }
    }
  3. LargeTextBox for display raw text data of an MIME message. Because some MIME messages contain multimedia data, their size could be significant. The WPF TextBlock control is not suitable to display large text data. Here the WPF FlowDocument control is used as it was found to perform quit well even when the text block loaded into it is very large.
  4. EMailMetaDetails contains EMailAttachments to handle attachments, routing information and Other information about a message.
    • Replying message information.
    • Message routing information.
    • An extension slot for more information extractable from the message MIME entity.

5. Serving HTTP Requests

5.1 The Server

The embedded HTTP server, which is implemented in one class called HttpContentServer, is controlled by its host (the present library). The "Start" method generates random port from 10000 to 60000, starts the "server" of type HttpListener and starts a thread, the "ServerThread" thread, which waits for the incoming requests.

The method "ServerThread" contains an infinite loop that passes the HTTP context object generated by the "server" to a GET handler. When in error, the server returns 500 status code.

private static void ServerThread()
{
    IsServing = true;
    serverstarting = false;
    HttpListenerContext ctx = null;
    while (!stopserving)
    {
        try
        {
            ctx = server.GetContext();
            if (ctx.Request.HttpMethod != "GET")
            {
                Return500(ctx);
                continue;
            }
            HandlerRequest(ctx);
        }
        catch (Exception ex)
        {
            System.Diagnostics.Trace.WriteLine(ex.Message);
            Return500(ctx);
        }
    }
    IsServing = false;
}

private static void Return500(HttpListenerContext ctx)
{
    HttpListenerResponse resp = ctx.Response;
    resp.StatusCode = 500;
}

5.2 Protocol Implementation

HTTP protocol requires a full server to handle GET, POST, HEAD, PUT, DELETE, TRACE, and CONNECT methods. In our application scenario, only GET will allowed to be used by the client. Other kind of requests will be ignored or treated as an error.

There are a rich set of status to reflect different state of the server in response to an request. The simple server shown here has only three states:

  1. Success (code 200),
  2. Not found (code 404), and
  3. Error (code 500).

It certainly can be expanded to have more states to be handled by the client in the future.

The server has two modes "Message" and "Data", it is to be set by the host before serving data.

  • Message mode: When serving top level MIME message part (MIME node), the server is set to Message mode.
  • Data mode: When serving multimedia data that is not contained in a MIME entity for a message, the server should be set to Data mode and with MediaBuffer being initialized and loaded by the host.
private static void HandlerRequest(HttpListenerContext ctx)
{
    HttpListenerRequest req = ctx.Request;
    HttpListenerResponse resp = ctx.Response;
    if (State == HttpContentServerState.Data)
    {
        string ct = ContentTypeString;
        if (ct.IndexOf(';') != -1)
            ct = ct.Substring(0, ct.IndexOf(';'));
        resp.StatusCode = 200;
        resp.Headers.Add("Cache-Control: no-cache");
        resp.ContentType = ct.ToLower();
        resp.ContentLength64 = MediaBuffer.Length;
        SendData(resp, MediaBuffer);
    }
    else
        ServeMessage(ctx);
}

After specifying the correct HTTP Content-Type, the data is sent back to the browser to have it displayed. For a simple server, a user can let the HttpContentServer to add other HTTP response headers accordingly.

5.3 Server Data Source

Normal the initial data for a http server in preparing a response (to a request) is most likely a pre-existing file on the local file system of the server. The present embedded http server obtains the data from a set of common buffers/objects that are updated for each different request. The host is responsible for loading the correct data into the them, depending on the mode of the server:

  • Message mode: the field "CurrentEntity" MIME entity (tree) should be updated to correspond to the current message.
  • Data mode: the field "MediaBuffer" buffer should be updated to correspond to the current media item.

5.4 Browser Cache Off

HTTP server can dictates how a client is going to save the returned data in its cache by using Cache-Control header. In out present application scenario, the client (browser) should not be allowed to cache any data, so the following response header is added:

resp.Headers.Add("Cache-Control: no-cache");

for all responses.

This is because our initial request uri is always '/' for all initial requests (each subsequent request can has an identifiable URI, please browse the code for such details), in which case the browser will never know when to use the cached content and when not to.

5.5 MIME Related

MIME format specification is a too large topic to be discussed here. However, a few points that are relevant to the current topic is given below:

  1. Message decoding problems. There are email clients that make mistakes in specifying the character encoding, like misspelling of the encoding names. A user can fix these problems by specifying transformation rules inside "LumiSoft.Net.Mime.dll.config" configuration file under the section "userSettings/LumiSoft.Net.Properties.Settings" where the setting's name is "EncodingMapps".

    The format of the transformation rule is

    • [rule]  :=  [wrong names] '->' {correct name}(';' [wrong names] '->' {correct name})*
    • [wrong names]  :=  {wrong name}(',' {wrong name})*

    Here those items inside [] are none-terminal nodes, those items inside {} are place holders for string values. {wrong name} is a place holder for the encoding name in error and {correct name} is the place holder for the correct encoding name, and those items inside single quote '' are concrete string values. A regular expression like syntax is used for pattern repetition, namely ()* stands for the pattern in brackets can be repeated zero to any time.

    To make it less formal, the included sample configuration file contains an example for one rule, which is currently used in our related products. More rules can be added by adding string ";" followed by similar rule to the existing one. The wrong names are a list of wrong name separated by ",".

  2. Problems in displaying none-ASCII characters. There are homebrew email clients that does not follow the MIME specifications by embedding non-ASCII texts directly into the message body and/or message header. Since the user is most like to download a message using a client that talks to the server in the so called Post Office Protocol (POP), only ASCII character can be transmitted reliably, the downloaded message will most likely to be irreversibly corrupted, which can not be recovered. The reason they can be displayed correctly in a web based interface is because modern web servers do not have to retrieve the message using the POP3 protocol.

For a user having a question to the effect that "how to traverse the MIME entity to display the message?", he/she could find part of the answer inside the HTTP server class.

6. Using the code

Included is a simple demo WPF programs using which a user can load a pre-existing MIME message on his/her hard disk for an visual display. There is no message header information on top since these information are supposed to be supplied by the message list host (of it) in our application scenarios. For a fuller display, a user can click the expansion button on the top right corner to open the reader.

Figure: Reading subscribed email messages from the CodeProject

To use the present library in a user's own code, one must compile the library first and then place the out put DLLs in a plug-in folder that the user find suitable. The host of the library need to have a way to find or define the plug-in directory and load it, like what is done in the following:

public partial class AppWindow : Window
{
    [Import(typeof(IEMailReader))]
    private IEMailReader reader
    {
        get;
        set;
    }

    public AppWindow()
    {
        InitializeComponent();
        // capture the folder selection event
        AddHandler(ComponentEvents.TransCompSelectFolderEvent, 
                  new TransCompSelectFolderEventHandler(OnDepositionSelectFolder));
        string pluginDir = ...;
        if (System.IO.Directory.Exists(pluginDir))
            LoadReader(pluginDir);
    }
    
    ....

    private void LoadReader(string pluginDir)
    {
        DirectoryCatalog categ = new DirectoryCatalog(pluginDir);
        string cname = 
              AttributedModelServices.GetContractName(typeof(IEMailReader));
        System.Linq.Expressions.Expression<Func<ExportDefinition, bool>>
                                exp = a => a.ContractName == cname;
        ImportDefinition id = new ImportDefinition(exp, 
                                cname, ImportCardinality.ExactlyOne, true, true);
        List<Tuple<ComposablePartDefinition, ExportDefinition>> 
                                _plugins = categ.GetExports(id).ToList();
        if (_plugins.Count == 1)
        {
            var cc = new CompositionContainer(categ);
            cc.ComposeParts(this);
            reader.QueryThread = OnQueryThread;
            //...
            //asign it to the visual place holder
            //...
            //signal the success of loading
        }
        else if (_plugins.Count == 0)
        {
            creader.Visibility = System.Windows.Visibility.Collapsed;
            System.Windows.MessageBox.Show("Failed to find any plug-in!");
        }
    }
    ...
    ...
    // the user's own logic to generate message thread tree
    private bool OnQueryThread(string MsgID, out ThreadedMessage StartMsg)
    {
        ...
        StartMsg = ...;
        return true;
    }
    ...
    ...
}

where the event TransCompSelectFolderEvent handler is attached and message thread query delegate QueryThread is attached.

7. Point of Interest

Writing a MIME reader is not hard in the way it is presented here. What's harder is how to deal with the large amount emails coming from various sources today.

The idea of using a small single purpose embedded HTTP server can be applied to many other HTML base offline dynamic or non-file based content display system as well.

8. Final Note 

The demo program is only for a demonstration on how to interact with the plug-in library. For that reason it was kept as simple as possible to show the points. To have an email reader application that can be put to real use, one need to write his/her own host and hopefully use this plug-in as the message display means.

It is welcomed to modify the library for your own needs, but it is also appreciated if your modifications and/or improvements can be feedback to the author.

For a user who don't have something handy to download emails to test it, although he/she absolutely does not have to since it was developed by us, he/she still has an option to install this freeware to accomplish it. The said program actually use this plugin to display emails.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here