Опубликован: 11.09.2006 | Доступ: свободный | Студентов: 7649 / 473 | Оценка: 4.26 / 3.45 | Длительность: 30:46:00
ISBN: 978-5-9556-0080-2
Лекция 3:

Работа с элементами управления (продолжение)

Получение сообщений – проект Mail

Классы для обработки исключений Exceptions
Класс CoreException.cs
using System;

namespace Mail
{
  /// <summary>
  /// Класс для обработки  основных исключений.
  /// </summary>
  public class CoreException : ApplicationException
  {
 /// <summary>
 /// Инициализация исключения без дополнительных параметров.
 /// </summary>
 public CoreException() : base() 
 {
 }

 /// <summary>
        /// Инициализация исключения с дополнительным описанием.
 /// </summary>
 public CoreException(string message) : base(message) 
 {
 }

 /// <summary>
        /// Инициализация исключения с дополнительным описанием и внутренним исключением  
 /// </summary>
 public CoreException(string message, System.Exception inner) : base(message, inner) 
 {
 }   
  }
}
Листинг 3.5.
Класс DeadConnectionException.cs
using System;

namespace Mail
{
  /// <summary>
    /// Класс для обработки исключений установки связи.
  /// </summary>
  public class DeadConnectException : CoreException
  {
 /// <summary>
        /// Инициализация исключения без дополнительных параметров.
 /// </summary>
 public DeadConnectException() : base() 
 {
 }

 /// <summary>
        /// Инициализация исключения с дополнительным описанием.
 /// </summary>
 public DeadConnectException(string message) : base(message) 
 {
 }

 /// <summary>
        /// Инициализация исключения с дополнительным описанием и внутренним исключением.
 /// </summary>
 public DeadConnectException(string message, System.Exception inner) : base(message, inner) 
 {
 }   
  }
}
Листинг 3.6.
Класс ParseException.cs
using System;

namespace Mail
{
  /// <summary>
    /// Класс для обработки исключений, возникающих  в момент анализа ответа.
  /// <summary>
  public class ParseException: CoreException
  {
 public ParseException(string message) : base(message) 
 {
 }

 public ParseException(string message, System.Exception inner) : 
 base(message, inner) 
 {
 }   
  }
}
Класс ResponseException.cs
using System;

namespace Mail
{
  /// <summary>
    /// Класс для обработки исключений POP3Unit
  /// </summary>
  public class ResponseException : CoreException
  {
 public ResponseException(string message) : base(message) 
 {
 }

 public ResponseException(string message, System.Exception inner) : 
 base(message, inner) 
 {
 }   
  }
}
Класс ShutdownException.cs
using System;

namespace Mail
{
  /// <summary>
    /// Класс для обработки исключений POP3Unit
  /// </summary>
  public class ShutdownException: CoreException
  {
 public ShutdownException(string message) : 
 base(message) 
 {
 }

 public ShutdownException(string message, System.Exception inner) : 
 base(message, inner) 
 {
 }   
  }
}
Библиотека конвертирования Library
Класс FromQuotedPrintableTransform.cs
using System;
using System.Security.Cryptography;
using System.Text;

namespace Mail
{
  /// <summary>
  /// Конвертирование  CryptoStream.
  /// <summary>
  public class FromQuotedPrintableTransform : ICryptoTransform, IDisposable
  {
 // максимальный размер входного\выходного блока.
 const int MAX_BUF = 3;

 // буфер
 byte [] _buf = null;

 /// <summary>
        /// Значение, указывающее на возможность повторного использования текущей трансформации
 /// </summary>
 public bool CanReuseTransform
 {
   get
   {
  return true;
   }
 }

 /// <summary>
        /// Значение, указывающее на возможность трансформации  составных блоков.
 /// </summary>
 public bool CanTransformMultipleBlocks
 {
   get
   {
  return false;
   }
 }

 /// <summary>
 /// Возвращение выходного размера блока.
 /// </summary>
 public int OutputBlockSize
 {
   get
   {
  return MAX_BUF;
   }
 }

 /// <summary>
 /// Возвращение входного  размера  блока.
 /// </summary>
 public int InputBlockSize
 {
   get
   {
  return MAX_BUF;
   }
 }

 /// <summary>
 /// Удаление  всех элементов.
 /// </summary>
 public void Dispose()
 {
   _buf = null;
   GC.SuppressFinalize(this);
 }


 /// <summary>
 /// Конвертирование  указанного  участка  входящего массива байтов из quoted-printable (RFC 2045 (6.7)) 
 /// и копирование результата  в указанный участок выходного массива байтов. 
 /// </summary>
 /// <param name="inputBuffer">Входящий массив байтов.</param>
 /// <param name="inputOffset">Начальная отметка участка, который нужно конвертировать.</param>
 /// <param name="inputCount">Количество байтов после индекса начала участка.</param>
 /// <param name="outputBuffer">Выходной массив байтов, в который требуется записать результат.</param>
 /// <param name="outputOffset">Начальная отметка участка, после которого необходимо вписать результат.</param>
 /// <returns>Количество вписанных байтов.</returns>
 public int TransformBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset)
 {
   // append unparsed characters
   if (_buf != null)
   {
  byte [] tmp = new byte[inputCount + _buf.Length];
  Array.Copy(_buf, 0, tmp, 0, _buf.Length);
  Array.Copy(inputBuffer, inputOffset, tmp, _buf.Length, inputCount);
  inputBuffer = tmp;

  inputCount = tmp.Length;
  _buf = null;
      } 
      else 
      {
        byte [] tmp = new byte[inputCount];
        Array.Copy(inputBuffer, inputOffset, tmp, 0, inputCount);
        inputBuffer = tmp;
      }

      int c = 0;
      int obi = outputOffset;

      while (c < inputCount)
      {
        byte cb = inputBuffer[c++];
        // skip CRLFs
        if (cb == '=') 
        {
          // impossible to get next 2 bytes, save unparsed characters 
          // for next session
          if (c + 1 >= inputCount) 
          {
            int len = inputCount - c;
            _buf = new byte[len + 1]; // +1 is for '='
            Array.Copy(inputBuffer, c - 1, _buf, 0, len + 1);
            break;
          } 
                    
          // skip =\r\n
          if (!(inputBuffer[c] == '\r' && inputBuffer[c + 1] == '\n'))
          {
            // TODO Add check. Uppercase letters must be used (=DD);  
            // lowercase letters are not allowed. 
            try
            {
              byte b = Convert.ToByte(Encoding.ASCII.GetString(inputBuffer, c, 2), 16);
              outputBuffer[obi++] = b;
            } 
            catch (FormatException e)
            {
              throw new ParseException("Incorrect sequence. Are you sure that it's quoted-printable?", e);
            }
          }

          // take next sequence
          c += 2; 
        } 
        // incorrect characters for quoted-printable, just skip it
        else if (!(cb == '\r' || cb == '\n')) 
        {          
          outputBuffer[obi++] = cb;
        }
      }

      return obi - outputOffset;
    }

    /// <summary>
    /// Конвертирование  указанного участка  входящего массива байтов из quoted-printable (RFC 2045 (6.7)).
    /// </summary>
    /// <param name="inputBuffer">Входящий массив байтов.</param>
    /// <param name="inputOffset">Начальная отметка участка, который нужно конвертировать.</param>
    /// <param name="inputCount">Количество байтов  после индекса начала участка.</param>
    /// <returns>Полученный массив байтов.</returns>
    public byte[] TransformFinalBlock(byte[] inputBuffer, int inputOffset, int inputCount)
    {
      byte [] b = new byte[inputCount];
      int s = TransformBlock(inputBuffer, inputOffset, inputCount, b, 0);

      byte [] c = new byte[s];
      Array.Copy(b, 0, c, 0, s);
      return c;
    }
  }
}
Листинг 3.7.
Класс Utils.cs
using System;
using System.Text.RegularExpressions;
using System.Collections;
using System.Text;
using System.Web;

namespace Mail
{
  /// <summary>
  /// Класс, содержащий общие функции.
  /// </summary>
  public class Utils
  {
    // Архив кодировок.
    static Hashtable knownEncodings = new Hashtable();

    static Utils()
    {

    }
    /// <summary>
    /// Извлечение  простого текста из HTML-текста.
    /// </summary>
    /// <param name="html">HTML текст.</param>
    /// <returns>Простой текст.</returns>
    public static string ExtractTextFromHtml(string html)
    {
      // С помощью регулярных выражений удаляем или заменяем
      // HTML-теги.
      // удаление  <!DOCTYPE ... >
      Regex r = new Regex(@"<![^>]+>"); // Создание регулярного выражения.
      html = r.Replace(html, ""); // Замена подходящей части текста на пустую строку.

      // удаление <head>...</head>
      r = new Regex(@"<head>.*?</head>", RegexOptions.IgnoreCase); // Создание регулярного выражения.
      // r = new Regex(@"<style[^>]+[>].*?</style>", RegexOptions.IgnoreCase);
      html = r.Replace(html, ""); // Замена подходящей части текста на пустую строку.
      // представляем, что </div>, <br />, </p> — это новая строка
      r = new Regex(@"(</div>|<[/]?br[^>]+>|</p>)", RegexOptions.IgnoreCase); // Создание регулярного выражения.
      html = r.Replace(html, "\r\n"); // Замена подходящей части текста на символы перехода на новую строку.

      // удаление всех тегов <...>
      r = new Regex(@"<[^>]+>", RegexOptions.Multiline); // Создание регулярного выражения, удаляющего все оставшиеся теги.
      html = r.Replace(html, ""); 
            html = HttpUtility.HtmlDecode(html);
      return html;
    }

    /// <summary>
    /// Возвращение  кодировки текста.
    /// </summary>
    /// <param name="charset">Текст, содержащий название кодировки.</param>
    /// <returns></returns>
    public static Encoding GetEncoding(string charset)
    {
      // Проверяем, есть ли данная кодировка в памяти класса,
      // и если есть, возвращаем ее.
      if (knownEncodings.ContainsKey(charset)) 
      {
        return (Encoding)knownEncodings[charset];
      }
      // Если кодировка не обнаружена,  начинаем анализировать строку.
      Encoding e = Encoding.Default;
      try
      {
        e = Encoding.GetEncoding(charset);
      }
      catch {}

      // Добавляем кодировку в память класса.
      knownEncodings.Add(charset, e);
      // Возвращаем кодировку.
      return e;
    }


    /// <summary>
    /// Исключаем "byte-stuffed", следуя RFC1939 
    /// \r\n.[not \r\n] => \r\n[not \r\n]
    /// </summary>
    /// <param name="s"></param>
    /// <returns></returns>
    public static string RemoveByteStuffedSequence(string s)
    {
      Regex r = new Regex(@"(?<=\r\n)\.(?!\r\n)");
      return r.Replace(s, "");
    }

    /// <summary>
        /// Декодируем строку
    /// </summary>
    /// <param name="s">Строка</param>
    /// <returns></returns>
    public static string DecodeQuotedPrintable(string s, Encoding e)
    {
      _curEncoding = e;
      Regex re = new Regex(@"=([a-fA-F0-9]{2})");
      return re.Replace(s, new MatchEvaluator(ReDigit));
    }


    static Encoding _curEncoding;
    /// <summary>
        /// Конвертирует "ХХ" в байтовый аналог
    /// </summary>
    /// <param name="match">"XX" формат</param>
    /// <returns></returns>
    static string ReDigit(Match match)
    {
      byte [] tmp = {Convert.ToByte(match.Groups[1].Value, 16)};
      return "" + _curEncoding.GetString(tmp);
    }

    /// <summary>
    /// RFC 2047
    /// </summary>
    /// <param name="s"<</param>
    /// <returns<</returns>
    public static string WordDecoder(string input)
    {
      string charset = "";
      string src;
      string tmp;
      src = input;
      Match m = Regex.Match(input, @"(?<ew>(?<n>\=\?(?<charset>[^?]+)\?(?<encoding>[QqBb])\?(?<content>[^?]+)\?\=)(\r\n)*\s*)(?=(?<isnext>\=\?[^?]+\?[QqBb]\?[^?]+\?\=)*)", RegexOptions.Multiline);
      if (m.Success)
      {
        while (m.Success)
        {
          charset = m.Groups["charset"].Value;
          string encoding = m.Groups["encoding"].Value.ToLower();
          switch(encoding)
          {
            case "q":
              tmp = m.Groups["content"].Value.Replace("_", " ");
              tmp = DecodeQuotedPrintable(tmp, GetEncoding(m.Groups["charset"].Value));
              break;

            case "b":
              tmp = GetEncoding(charset).GetString(Convert.FromBase64String(m.Groups["content"].Value));
              break;

            default:
              throw new ParseException("Неизвестный метод кодировки");
          }

          src = src.Replace(((m.Groups["isnext"].Value.Length == 0) ? m.Groups["n"].Value : m.Groups["ew"].Value), tmp);
          m = m.NextMatch();
        }

        return src; 
      }

      return GetEncoding(charset).GetString(Encoding.Default.GetBytes(src));
    }
  }
}
Листинг 3.8.
Формирование сообщений
Класс MessageFile.cs
namespace Mail.Providers
{
  using System;
  using System.IO;
  using System.Diagnostics;
  using System.Text;

  /// <summary>
  /// Провайдер для .eml-файлов.
  /// </summary>
  public class MessageFile : Provider
  {
    const string FiveOctalTerm = "\r\n.\r\n";

    FileStream fs = null;

    /// <summary>
    /// Конструктор.
    /// </summary>
    /// <param name="filename">Адрес к файлу.</param>
    public MessageFile(string filename)
    {
            fs = new FileStream(filename, FileMode.Open, FileAccess.Read);
      TempDirectory = Path.GetTempPath();
    }

    /// <summary>
        /// Не реализовано
    /// </summary>
    /// <param name="i"></param>
    public override void DeleteMessage(uint index)
    {
            Debug.WriteLine("Не реализовано");
    }

    string TruncateTail(string message)
    {
      if (!message.EndsWith(FiveOctalTerm))
      {
                Debug.WriteLine("Последние 5 символов: {" + message.Substring(message.Length - 5) + "}");
                throw new ResponseException("Неправильные символы конца сообщения.");
      }

      return message.Remove(message.Length — FiveOctalTerm.Length, FiveOctalTerm.Length);
    }

    /// <summary>
    /// Не реализовано.
    /// </summary>
    /// <param name="i"></param>
    public override Message GetMessage(uint index)
    {
      byte [] buf = new byte[fs.Length];
      fs.Read(buf, 0, buf.Length);
      fs.Position = 0;

      string message = Utils.RemoveByteStuffedSequence(Encoding.ASCII.GetString(buf));
      return new Message(this, message, index);
    }

    /// <summary>
    /// Этот метод необязателен.
    /// </summary>
    /// <param name="name"></param>
    /// <param name="pass"></param>
    public override void LogIn(string name, string pass)
    {
            Debug.WriteLine("Не реализовано");
    }

    /// <summary>
    /// Закрытие потока.
    /// </summary>
    public override void Dispose()
    {
      try
      {
        Quit();
      } 
      catch
      {
      }

            GC.SuppressFinalize(this);      
    }

    /// <summary>
    /// Закрытие FilеStream.
    /// </summary>
    public override void Quit()
    {
            fs.Close();
    }
  }
}
Листинг 3.9.
Класс MaildropStatus.cs
using System.Collections;

namespace Mail.Providers
{
  /// <summary>
  /// Содержание информации о почтовом ящике  (размер и количество сообщений).
  /// </summary>
  class MaildropStatus
  {
    internal uint messages;
    internal uint size;
//    internal Hashtable messagelist;

    /// <summary>
        /// Конструктор с параметрами: количество сообщений и размер.
    /// </summary>
        /// <param name="messages">Количество сообщений.</param>
    /// <param name="size">Размер сообщений.</param>
    public MaildropStatus(uint messages, uint size)
    {
      this.messages = messages;
      this.size = size;
    }
  }
}
Листинг 3.10.
Класс Pop3.csM
//#define _DEBUG

using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Diagnostics;
using System.Collections;
using System.Text;
using System.Text.RegularExpressions;

namespace Mail.Providers
{
  /// <summary>
  /// Реализация протокола POP3
  /// </summary>  
  /// <example>Пример получения сообщения.
  /// <code>
  /// using (Pop3 pop3 = new Pop3("host"))
  ///  {
  ///    pop3.LogIn("Username", "Password");
  ///    Console.WriteLine("Количество сообщений:" + pop3.NumberOfMessages);
  ///    
  ///    using(Message msg = pop3.GetMessage(1)) // получение первого сообщения
  ///    {
  ///      Console.WriteLine("Тема: " + msg.Subject); 
  ///    }
  ///  }
  /// </code>
  /// </example>  
  public class Pop3 : Provider
  {
    #region Constants
    const int MaxReceiveSize = 1024;
    const int POP3DefaultPort = 110;

    const int SendTimeout = 60000; // в милисекундах.
        public int ReceiveTimeout = 2000000; // в милисекундах.
        public int PollTimeout = 100000; // в микросекундах.

    const string CRLF = "\r\n";
    
    const string FiveOctalTerm = "\r\n.\r\n";
    const string STAT_OK = "+OK";
    const string STAT_ERR = "-ERR";

    const char SPACE = ' ';
    const char CR = '\r';
    #endregion

    Socket _socket = null;

        MaildropStatus _status = null;
    uint [] _messagelist = null;

    bool _authenticated = false;

    #region DEBUG functions
    FileStream GetDebugStream()
    {
      return new FileStream(@"C:\trace_response.log", FileMode.Append);
    }
    [Conditional("_DEBUG")]
    void Trace(byte [] str)
    {
      FileStream fs = GetDebugStream();
      fs.Write(str, 0, str.Length);
      fs.Close();
    }

    [Conditional("_DEBUG")]
    void Trace(string str)
    {
      FileStream fs = GetDebugStream();
      fs.Write(Encoding.ASCII.GetBytes(str), 0, str.Length);
      fs.Close();
    }
    #endregion

    #region Constructors 
    /// <summary>
        /// Инициализация класса установленным по умолчанию портом (110) и установка  адреса временной папки 
        /// на текущую системную временную папку.    
    /// </summary>
    /// <param name="server">IP адрес сервера.</param>
    public Pop3(string server)
    {
      _server = server;
      _port = POP3DefaultPort;
      TempDirectory = Path.GetTempPath();
    }

    /// <summary>
        /// Инициализация класса а установленным по умолчанию портом (110).
    /// </summary>
        /// <param name="server">IP адрес сервера.</param>
    /// <param name="temp">Адрес временной папки.</param>
    public Pop3(string server, string temp)
    {
      _server = server;
      _port = POP3DefaultPort;
      TempDirectory = temp;
    }

    /// <summary>
    /// Инициализация класса .
    /// </summary>
        /// <param name="server">IP адрес сервера.</param>
    /// <param name="port">Номер порта.</param>
    public Pop3(string server, int port)
    {
      _server = server;
      _port = port;
      TempDirectory = Path.GetTempPath();
    }

    /// <summary>
        /// Инициализация класса .
    /// </summary>
        /// <param name="server">IP-адрес сервера.</param>
        /// <param name="port">Номер порта.</param>
        /// <param name="temp">Адрес временной папки.</param>
    public Pop3(string server, int port, string temp)
    {
      _server = server;
      _port = port;
      TempDirectory = temp;
    }
    #endregion

    #region Public properties
    /// <summary>
    /// Возвращается значение  true, если в почтовом ящике есть сообщения.
    /// </summary>
    public bool IsMessages
    {
      get 
      {
        return (NumberOfMessages > 0);
      }
    }

    /// <summary>
    /// Количество сообщений в ящике.
    /// </summary>
    public uint NumberOfMessages
    {
      get 
      {
        // not initialized
        if (_status == null)
        {
          GetStatus();
        }

        return _status.messages;
      }  
    }
    #endregion

    #region Method-Property substitution
        /// <summary>
        /// Получение количества сообщений.
        /// </summary>
        /// <returns></returns>
    public uint GetNumberOfMessages()
    {
            return NumberOfMessages;
    }
        
    public bool GetIsMessages()
    {
      return IsMessages;
    }
    #endregion

    /// <summary>
        /// Анализ количества  строк, полученных  от сервера после отправки команды STAT.    
    /// </summary>
        /// <example>Команда STAT. В ответ на вызов команды сервер выдает положительный ответ "+OK", 
        /// за которым следует количество сообщений в почтовом ящике и их общий размер в символах. 
        /// Сообщения, которые помечены для удаления, не учитываются в ответе сервера. 
        /// </example>
    void GetStatus()
    {
      CheckConnection();

      Send("STAT");
      string tmp = Receive();

      string [] tokens = tmp.Split(new Char[] {SPACE, CR}, 4);

      try
      {
        _status = new MaildropStatus(
          Convert.ToUInt32(tokens[1], 10), 
          Convert.ToUInt32(tokens[2], 10)
        );
      } 
      catch (Exception e)
      {
                throw new CoreException("Невозможно проанализировать ответ", e);
      }
    }
        /// <summary>
        /// Установка соединения с сервером.
        /// </summary>
        /// <param name="server">Название сервера.</param>
        /// <param name="port">Номер порта.</param>
    void EstablishConnection(string server, int port)
    {
      // Получение IP-адреса сервера.
      IPAddress ipadr = Dns.Resolve(server).AddressList[0];
      IPEndPoint ephost = new IPEndPoint(ipadr, port);
 
      // Создание Socket для передачи данных по протоколу TCP.
      _socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

      
      LingerOption linger = new LingerOption(true, 10);
      _socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Linger, linger);
      // Установка времени ожидания.
      _socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.SendTimeout, SendTimeout);
      _socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout, ReceiveTimeout);

      // Соединение с сервером.
      _socket.Connect(ephost);
      if (!_socket.Connected)
      {
        throw new CoreException("Сервер не найден: " + server);
      }
    }

    /// <summary>
    /// Проверка соединения и авторизации пользователя.
    /// </summary>
    void CheckConnection()
    {
      if (_socket == null || !_socket.Connected) 
      {
        throw new CoreException("Соединение не установлено.");
      }

      if (!_authenticated)
      {
                throw new CoreException("Пользователь не аутентифицирован (Метод LogIn). ");
      }
    }
        /// <summary>
        /// Отправка команды на сервер.
        /// </summary>
        /// <param name="command">Текст команды.</param>
    void Send(string command)
    {
            // Все команды заканчиваются парой CRLF.
      command += CRLF;
            // Сервер работает с кодировкой ASCII.
      Encoding tmp = Encoding.ASCII;
      Byte [] buf = tmp.GetBytes(command);

      int total = buf.Length;
      while (total > 0)
      {
        total -= _socket.Send(buf, command.Length, SocketFlags.None);
      }
    }
        /// <summary>
        /// Анализ POP3-строки.
        /// </summary>
        /// <param name="str">Строка.</param>
    void AnalyseResponse(string str)
    {
      Trace(str);
//      Debug.WriteLine(str);

      if (str.StartsWith(STAT_ERR))
      {
        string msg;        
        int i = str.IndexOf(CRLF);
        if (i < 0) 
        {
          msg = "Ответ сервера: " + STAT_ERR;
        } 
        else
        {
          // Если ответ слишком большой, отсекаем  его.
          msg = str.Substring(STAT_ERR.Length + 1, Math.Min(i - STAT_ERR.Length - 1, 79));
        }

        throw new ResponseException(msg);
      }
    }

    /// <summary>
    /// Получение сообщения в POP3-формате.
    /// </summary>
    /// <param name="index">Номер сообщения.</param>
    /// <returns></returns>
    public StringReader DumpMessage(int index)
    {
      CheckConnection();

      Send("RETR " + index);
      return new StringReader(Receive());
    }
        /// <summary>
        /// Удаление символов конца сообщения.
        /// </summary>
        /// <param name="message">Сообщение.</param>
        /// <returns></returns>
    string TruncateTail(string message)
    {
      if (!message.EndsWith(FiveOctalTerm))
      {
        Debug.WriteLine("Последние 5 символов: {" + message.Substring(message.Length — 5) + "}");
        throw new ResponseException("Неправильные символы конца сообщения.");
      }

            return message.Remove(message.Length — FiveOctalTerm.Length, FiveOctalTerm.Length);
    }

    /// <summary>
    /// Получение существующих номеров сообщений.
    /// </summary>
    /// <returns>Массив с существующими индексами сообщений.</returns>
        /// <example>Команда LIST. Сервер выдает информацию о всех сообщениях, находящихся в почтовом ящике. 
        /// Сообщения, помеченные для удаления, не перечисляются. 
        /// </example>
    public uint[] ListMessages()
    {
      CheckConnection();

      if (_messagelist == null)
      {
        Send("LIST");
        string tmp = Receive();
        tmp = TruncateTail(tmp);
        int start = tmp.IndexOf(CRLF);
        if (start > 0)
        {
          start += CRLF.Length;
          ArrayList l = new ArrayList();
          Regex r = new Regex(@"\r\n");
          string [] list = r.Split(tmp.Substring(start));
          if (list.Length > 0)
          {
            foreach (string s in list)
            {
              string [] f = s.Split(new char [] {' '}, 2);
              l.Add(Convert.ToUInt32(f[0], 10));
            }
          }

          if (l.Count > 0)
          {
            _messagelist = (uint [])l.ToArray(typeof(uint));
          }
          else
            _messagelist = new uint[0];
        } else 
          _messagelist = new uint[0];
      }

      return _messagelist;
    }

    /// <summary>
    /// Отправляет команду NOOP на сервер. 
    /// </summary>
    /// <remarks>
    /// <para>Используется для поддержания сеанса с сервером.</para>
    /// </remarks>
        /// <example>Команда NOOP. POP3-сервер ничего не делает и всегда отвечает положительно. 
        /// </example>
    public void SendNoop()
    {
      CheckConnection();

      Send("NOOP");
      string tmp = Receive();
    }

    /// <summary>
    /// Возвращает уникальный идентификатор сообщения.
    /// </summary>
    /// <remarks>
        /// <para>
        /// Если сообщение помечено на удаление, оно не учитывается.
    /// </para>
    /// </remarks>
    /// <param name="index">Номер сообщения.</param>
    /// <returns>Уникальный идентификатор пользователя.</returns>
    public string GetMessageUniqueID(uint index)
    {
      CheckConnection();

      Send("UIDL " + index);
      string tmp = Receive();

      string [] f = tmp.Split(new char [] {' ', '\r', '\n'}, 4);
            return f[2];            
    }

    /// <summary>
    /// Получение заголовка сообщения.
    /// </summary>    
    /// <param name="index">Сообщение.</param>
    /// <param name="liens">Количество первых строк.</param>
    /// <returns>Сообщение с анализированными заголовками.</returns>
        /// <example>Команда TOP. Если ответ сервера положительный, 
        ///  он передает заголовки сообщения и указанное количество строк из тела сообщения.
        /// </example>
    public Message GetMessageHeader(uint index, int top)
    {
      CheckConnection();

      Send("TOP " + index + " " + top);
      string message = Receive();

      message = Utils.RemoveByteStuffedSequence(message);
      return new Message(this, TruncateTail(message), index);
    }

    /// <summary>Удаление сообщения.
    /// </summary>
    /// <param name="index">Номер сообщения.</param>
        /// <example>Команда DELETE. POP3-сервер помечает указанное сообщение как удаленное, 
        /// но не удаляет его, пока сессия не перейдет в режим UPDATE. 
        /// </example>
    public override void DeleteMessage(uint index)
    {
      CheckConnection();

      Send("DELE " + index);
      string tmp = Receive();
    }

    /// <summary>
    /// Получение сообщения.
    /// </summary>
    /// <param name="index">Номер сообщения.</param>
    /// <returns>Сообщение.</returns>
        /// <example>Команда RETR. После положительного ответа сервер передает содержание сообщения.        
        /// </example>
    public override Message GetMessage(uint index)
    {
      CheckConnection();

            Send("RETR " + index);
            string message = ReceiveMessage();

      message = Utils.RemoveByteStuffedSequence(message);
            return new Message(this, TruncateTail(message), index);
    }

    public void OnRecievedData( IAsyncResult ar )
    {
    }

        /// <summary>
        /// Получение ответа сервера без проверки подлинности.
        /// </summary>
        /// <returns>Ответ сервера.</returns>
    StringBuilder UnsafeReceive()
    {
      StringBuilder tmp = new StringBuilder();
      Encoding cenc = Encoding.ASCII;
      IAsyncResult asynResult;
      byte[] buf = new byte[1024];
      int recv = 0;      
      do
      {
        asynResult = _socket.BeginReceive(buf, 0, buf.Length, SocketFlags.None, null, null);
        if (asynResult.AsyncWaitHandle.WaitOne())
        {
          recv = _socket.EndReceive(asynResult);
          string t = cenc.GetString(buf, 0, recv);
          tmp.Append(t);
          if (t.LastIndexOf(FiveOctalTerm) > 0) break;
        }
      }
      while(_socket.Poll(PollTimeout, SelectMode.SelectRead));

            return tmp;
    }
    /// <summary>
    /// Получение ответа сервера без проверки подлинности.
    /// </summary>
    /// <returns>Ответ сервера.</returns>
    StringBuilder UnsafeReceiveMessage()
    {
      StringBuilder tmp = new StringBuilder();
      Encoding cenc = Encoding.ASCII;
      IAsyncResult asynResult;
      byte[] buf = new byte[1024];
      int recv = 0;      
      do
      {
        asynResult = _socket.BeginReceive(buf, 0, buf.Length, SocketFlags.None, null, null);
        if (asynResult.AsyncWaitHandle.WaitOne())
        {
          recv = _socket.EndReceive(asynResult);
          string t = cenc.GetString(buf, 0, recv);
          tmp.Append(t);
          //if (t.LastIndexOf(FiveOctalTerm) > 0) 
          //  break;
        }
      }
      while(!tmp.ToString().EndsWith(FiveOctalTerm));

      return tmp;
    }
    /// <summary>
    /// Возвращение  ответа сервера.
    /// </summary>
    /// <returns>Ответ сервера.</returns>
    string Receive()
    {
      StringBuilder tmp = UnsafeReceive();
            string str = tmp.ToString();
      AnalyseResponse(str);

      return str;
    }
    /// <summary>
    /// Возвращение сообщения в виде строки.
    /// </summary>
    /// <returns></returns>
    string ReceiveMessage()
    {
      StringBuilder tmp = UnsafeReceiveMessage();
      string str = tmp.ToString();
      AnalyseResponse(str);

      return str;
    }
        /// <summary>
        /// Аутентификация пользователя.
        /// </summary>
        /// <param name="username">Имя пользователя.</param>
        /// <param name="password">Пароль.</param>
        /// <example>После установки соединения сервер находится в режиме авторизации пользователя.
        /// Пользователь должен идентифицировать себя на сервере, используя команды USER и PASS. 
        /// Сначала надо отправить команду USER, после которой в качестве аргумента следует имя пользователя. 
        /// Если сервер отвечает положительно, то теперь необходимо отправить команду PASS, за которой следует пароль.
        /// <code>
        /// Client: USER username
        /// Server: +OK username
        /// Client: PASS mypass
        /// Server: +OK username
        /// </code>
        /// </example>
    void AuthenticateYourSelf(string username, string password)
    {
      Send("USER " + username);
      Receive();
            Send("PASS " + password);
      Receive();

      _authenticated = true;
    }

    /// <summary>
        /// Соединение с сервером и аутентификация пользователя.
    /// </summary>
    /// <param name="username">Имя пользователя.</param>
    /// <param name="password">Пароль.</param>
    public override void LogIn(string username, string password)
    {
      try
      {
        if (_socket != null) 
        {
          Quit();
          ResetVariables();
        }
                // Установка соеденения.
        EstablishConnection(_server, _port);
        Receive(); // Получение приветствия от сервера.
        AuthenticateYourSelf(username, password);
      }
      catch (ShutdownException e)
      {
                throw new CoreException("Невозможно завершить предыдущий сеанс.", e);
      }
      catch (Exception e)
      {
        throw new CoreException("Вход невозможен", e);
      }
    }

    /// <summary>
    /// Закрытие транзакции на сервере.
    /// </summary>
    public override void Quit()
    {
      try
      {
        CheckConnection();
                // Сервер завершает POP3-сессию и переходит в режим UPDATE.
        Send("QUIT"); // Ответ нас не интересует
      }
      catch (Exception e)
      {
        throw new ShutdownException("Невозможно покинуть транзакцию", e);
      }

      CloseSocket();
    }

    /// <summary>
    /// Свойство закрытия соединения.
    /// </summary>
    void CloseSocket()
    {
      try
      {
        _socket.Shutdown(SocketShutdown.Both);
        _socket.Close();
        // Свойство 'Connected'  установлено в false, когда соединение закрыто.
        if (_socket.Connected) 
        {
          throw new CoreException("При закрытии socket возникло исключение: " + 
            Convert.ToString(System.Runtime.InteropServices.Marshal.GetLastWin32Error()));
        }

        _socket = null;
      }
      catch (SocketException e)
      {
                throw new CoreException("Невозможно закрыть socket", e);
      }
    }
        /// <summary>
        /// Сброс переменных.
        /// </summary>
    void ResetVariables()
    {
      _authenticated = false;
      _status = null;
      _messagelist = null;
    }

    /// <summary>
    /// Закрытие сеанса.
    /// </summary>
    public override void Dispose()
    {
      try
      {
        Quit();
        ResetVariables();
      } 
      catch // Обработчик всех возникших исключений.
      {
        Debug.WriteLine("Невозможно закрыть socket");
      }

      GC.SuppressFinalize(this);
    }
  }
}
Листинг 3.11.
Класс Provider.cs
namespace Mail.Providers
{
  using System;

  /// <summary>
    /// Общий абстрактный класс для всех провайдеров.  
  /// </summary>
  public abstract class Provider : IDisposable
  {
        /// <summary>
        /// Название сервера.
        /// </summary>
    protected string _server;
        /// <summary>
        /// Номер порта.
        /// </summary>
    protected int _port;
    /// <summary>
    /// Временная папка для записи временных файлов.
    /// </summary>
    string _tempdir;

    /// <summary>
    /// Метод авторизации пользователя.
    /// </summary>
    /// <param name="login">Имя пользователя.</param>
    /// <param name="password">Пароль.</param>
    public abstract void LogIn(string login, string password);

    /// <summary>
    /// Закрытие сеанса.
    /// </summary>
    public abstract void Quit();

    /// <summary>
    /// Удаление сообщения.
    /// </summary>
    /// <param name="index">Номер сообщения.</param>
    public abstract void DeleteMessage(uint index);

    /// <summary>
    /// Получение сообщения.
    /// </summary>
        /// <param name="index">Номер сообщения.</param>
    public abstract Message GetMessage(uint index);


    /// <summary>
    /// Путь к временной папке.
    /// </summary>
    public string TempDirectory
    {
      get
      {
        return _tempdir;
      }
      set
      {
        _tempdir = value;
      }
    }

    /// <summary>
    /// Уничтожение объекта.
    /// </summary>
    abstract public void Dispose();
  }
}
Листинг 3.12.
Обработка вложений. Класс AttachDescriptor.cs
using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;

namespace Mail
{
  /// <summary>
    /// Содержит методы и информацию о вложениях в письмо.
  /// </summary>
  public class AttachDescriptor
  {
    string _oldname;
    string _tmpfile;

    internal AttachDescriptor(string name, string dir)
    {
      _oldname = name;
      _tmpfile = dir + Guid.NewGuid();
    }
    /// <summary>
    /// Декодирование  файла.
    /// </summary>
    /// <param name="message">Текст сообщения с вложенным файлом.</param>
    /// <param name="transform">Формат трансформации.</param>
    internal void DecodeFile(string message, ICryptoTransform transform)
    {
      try 
      {
        // Создаем временный файл.
        FileStream tf = new FileStream(_tmpfile, FileMode.Create, FileAccess.Write);
        // Создаем поток трансформации для временного файла.
        CryptoStream cs = new CryptoStream(tf, transform, CryptoStreamMode.Write);
        
        // Конвертируем  строки в массив байтов
        Encoding enc = Encoding.ASCII;
        byte [] b = enc.GetBytes(message);
        // Записываем байты в поток трансформации.
        cs.Write(b, 0, b.Length);
        // Закрываем потоки.
        cs.Close();
        tf.Close();
      } 
      // Обрабатываем  возникшие исключения.
      catch(System.Exception e)
      {
        Console.WriteLine(e.ToString());
        throw new ParseException("Невозможно декодировать содержимое файла", e);
      }
    }
    /// <summary>
    /// Закрываем  и удаляем  временный файл.
    /// </summary>
    internal void Close()
    {
      File.Delete(_tmpfile);            
    }

    /// <summary>
        /// Возвращаем  файловый поток из  файла временного вложения.
    /// </summary>
    /// <returns></returns>
    public FileStream GetFile()
    {
      FileStream tf = new FileStream(_tmpfile, FileMode.Open, FileAccess.Read);
      return tf;
    }

    #region Public properties
        /// <summary>
        /// Название.
        /// </summary>
    public string Name
    {
      get
      {
                return _oldname;
      }
    }
        /// <summary>
        /// Временный файл.
        /// </summary>
    public string TempFile
    {
      get
      {
        return _tmpfile;
      }
    }
        /// <summary>
        /// Размер.
        /// </summary>
    public long Size
    {
      get
      {
        FileInfo fi = new FileInfo(_tmpfile);
        return fi.Length;
      }
    }
    #endregion
  }
}
Листинг 3.13.
Основной класс сообщения. Common.cs
namespace Mail
{
  using System;
  using System.Collections;
  using System.IO;
  using System.Text;
  using System.Text.RegularExpressions;
  using System.Security.Cryptography;

  using Mail.Providers;

  /// <summary>
  /// Основной класс для <see cref="Mime"/> и <see cref="Message"/>.
  /// Содержит общую информацию и различные методы.
  /// </summary>
  public class Common
  {
    internal const string DCRLF = "\r\n\r\n";
    internal const string CRLF = "\r\n";
    internal const char Colon = ':';
    internal const string MIMEE = "--";

    internal string _message;
    internal long _size;

    internal Provider _pop3;

    public Provider Parent
    {
      get
      {
        return _pop3;
      }      
    }

    /// <summary>
    /// Список sub-mimes.
    /// </summary>
    public Mime [] ChildMimes = new Mime[0];

    /// <summary>
    /// Заголовки.
    /// </summary>
    internal Hashtable Headers = new Hashtable();

    /// <summary>
        /// Содержит все заголовки из <see cref="Message"/> или <see cref="Mime"/>
    /// </summary>
    /// <remarks>
        /// Все заголовки должны быть в нижнем регистре.
    /// </remarks>
    public Hashtable AllHeaders
    {
      get
      {
                return Headers;
      }
    }

    /// <summary>
    /// Размер сообщения.
    /// </summary>
    public long GetSize()
    {
      return _size;      
    }
    /// <summary>
    /// Подготовка  строки в зависимости от кодировки сообщения.
    /// </summary>
    /// <param name="mes">Текст сообщения.</param>
    /// <returns></returns>
    internal string PreparedString(string mes)
    {
      string t;
      switch (TransferEncoding)
      {
        case "base64":
          t = DecodeMessage(mes, new FromBase64Transform());
          break;

        case "quoted-printable":
          t = DecodeMessage(mes, new FromQuotedPrintableTransform());
          break;

        default:
          t = mes;
          break;
      }

      return t;
    }
    /// <summary>
    /// Подготовка  тела сообщения.
    /// </summary>
    /// <returns></returns>
    internal string PreparedBody()
    {
      
      return PreparedString(_message);
    }
    /// <summary>
    /// Декодируем сообщение.
    /// </summary>
    /// <param name="message">Текст сообщения.</param>
    /// <param name="transform">Тип трансформации.</param>
    /// <returns></returns>
    string DecodeMessage(string message, ICryptoTransform transform)
    {
      MemoryStream tf = new MemoryStream();
      CryptoStream cs = new CryptoStream(tf, transform, CryptoStreamMode.Write);
        
      // конвертируем  строки в массив байтов
      Encoding enc = Encoding.ASCII;
      byte [] b = enc.GetBytes(message);
      cs.Write(b, 0, b.Length);

      cs.Close();
      string t = Utils.GetEncoding(Charset).GetString(tf.ToArray());
      tf.Close();
      return t;
    }
    /// <summary>
    /// Конструктор.
    /// </summary>
    /// <param name="parent">Родительский провайдер.</param>
    /// <param name="message">Текст, содержащий сообщение.</param>
    internal Common(Provider parent, string message)
    {
      if (parent == null)
      {
        throw new ArgumentNullException("parent");
      }
      if (message == String.Empty)
      {
        throw new ArgumentException("empty string", "message");
      }

      int end = FillHeaders(message);
      _size = message.Length;

      // исключаем заголовок из тела сообщения
      _message = message.Substring(end);
      _pop3 = parent;
    }

    /// <summary>
        /// Выбираем все заголовки и заполняем массив.
    /// </summary>
    /// <returns></returns>
    internal int FillHeaders(string message)
    {
      int start = 0; //message.IndexOf(CRLF) + CRLF.Length; //пропускаем 2 байта
      int headerend = message.IndexOf(DCRLF); 
      string headers = message.Substring(start, headerend - start);
      GetHeaders(headers);

      // пропускаем секцию заголовков
      headerend += DCRLF.Length;

      return headerend;
    }

    /// <summary>
    /// Заполнение  <see cref="Mime"/> массива.
    /// </summary>
    /// <returns></returns>
    protected bool MultipartMixed()
    {
      string b = GetBoundary();
      if (b == String.Empty) return false;

      int s = _message.IndexOf(b);
      if (s < 0)
      {
        throw new ParseException("Can't find beginning MIME boundary: " + b);
      }

      ArrayList tmimes = new ArrayList();
      
      while(true)
      {
        s += b.Length + CRLF.Length;

        int e = _message.IndexOf(b, s);
        if (e < 0)
        {          
          if (_message.IndexOf(MIMEE, s - CRLF.Length, MIMEE.Length) < 0)
          {
            throw new ParseException("Неправильный MIME");
          }

          break;
        }
                      

        tmimes.Add(new Mime(_pop3, _message.Substring(s, e - s - CRLF.Length)));
        s = e;
      }

      ChildMimes = (Mime [])tmimes.ToArray(typeof(Mime));
            
      return true;
    }


    /// <summary>
    /// Попытка извлечения значения 'boundary' из <see cref="ContentType"/>.
    /// </summary>
    /// <returns></returns>
    protected string GetBoundary()
    {      
      Regex r = new Regex("boundary=[\\\"]?([^\\r\\\"]+)");
      if (ContentType == null)
      {
                return String.Empty;
      }

      Match m = r.Match(ContentType);
      if (m.Success)
      {
        return "--" + m.Groups[1].ToString();
      }
      else
      {
        return String.Empty;
      }
    }



    /// <summary>
    /// Все заголовки в нижнем регистре.
    /// </summary>
    /// <param name="top">Заголовок</param>
    void GetHeaders(string top)
    {      
      Regex line = new Regex(@"\r\n(?![\t\x20])");
      string [] col = line.Split(top);
      foreach (string s in col)
      {
        string [] fields = s.Split(new Char[] {':'}, 2);
        //        Console.WriteLine(fields[0] + "}={" + fields[1] + "}");
        if (fields.Length < 2) continue;
        fields[0] = fields[0].ToLower(); // перевод в нижний регистр
        fields[1] = fields[1].TrimStart(' '); // удаление ненужных пробелов

        
        if (Headers.ContainsKey(fields[0]))
        {
          object oldv = Headers[fields[0]];
          ArrayList al = oldv as ArrayList;
          if (al == null)
          {
            al = new ArrayList();
            al.Add(oldv); 
            Headers[fields[0]] = al;
          } 

          al.Add(fields[1]);
        } 
        else 
        {
          Headers.Add(fields[0].ToLower(), fields[1]);
        }
      }
    }

    #region Common headers
    public string Charset
    {
      get
      {
        Regex r = new Regex(@"charset=[""'\s]([^""'\s]+)");
        Match m = r.Match(ContentType);
        if (m.Success)
          return m.Groups[1].Value;
        else
          return "";
      }
    }

    protected string TransferEncoding
    {
      get
      {
        return ((string)Headers["content-transfer-encoding"]).ToLower();
      }
    }

    /// <summary>
    /// Содержит тип текущей <see cref="Mime"/> секции или <see cref="Message"/>.
    /// </summary>    
    public string ContentType
    {
      get
      {
        return (string)Headers["content-type"];
      }
    }
    #endregion
  }
}
Листинг 3.14.
Класс Message.cs
namespace Mail
{
  using System;
  using System.Text;
  using System.Text.RegularExpressions;
  using System.Collections;
  using System.Diagnostics;

  using Mail.Providers;
  /// <summary>
  /// Класс, который описывает сообщение, полученное с сервера.  
  /// </summary>
  public class Message : Common, IDisposable
  {
    string _body;
    // Тип тела сообщения.
    BodyTypes _body_type = BodyTypes.Unknown;
    // Массив вложений.
    AttachDescriptor [] _attaches = null;

    /// <summary>
    /// Номер сообщения.
    /// </summary>
    public uint Index;


    /// <summary>
    /// Создание  нового сообщения.
    /// </summary>
    /// <param name="parent">Ссылка на провайдер.</param>
    /// <param name="message">Текст сообщения, которое необходимо проанализировать.</param>
    /// <param name="index">Номер сообщения.</param>
    public Message(Provider parent, string message, uint index) : base(parent, message)
    {
      // Если индекс сообщения меньше нуля, то генерируется исключение типа ArgumentOutOfRangeException
      if (index < 1)
      {
        throw new ArgumentOutOfRangeException("index");
      }

      Index = index;
      ParseContentType();
    }

    /// <summary>
    /// Вложенные файлы.
    /// </summary>
    public AttachDescriptor [] Attachments
    {
      get
      {
        if (_attaches == null)
        {
          ArrayList al = new ArrayList();
                    GetAllAttachments(ChildMimes, al);
          _attaches = (AttachDescriptor [])al.ToArray(typeof(AttachDescriptor));
        }

        return _attaches;
      }
    }

    /// <summary>
    /// Получение всех вложений.
    /// </summary>
    /// <param name="mimes"></param>
    void GetAllAttachments(Mime [] mimes, ArrayList al)
    {
      foreach (Mime m in mimes)
      {
        if (m.ChildMimes.Length == 0)
        {
          if (m._attach != null) al.Add(m._attach);
        } 
        else
        {
          GetAllAttachments(m.ChildMimes, al);
        }
      }
    }
  
    // Анализ типа сообщения.
    void ParseContentType()
    {
      if (ContentType == null)
      {
                throw new ParseException("Определение типа сообщения (Content-Type пуст)");
      }

      string type;
      int i = ContentType.IndexOf(";");
      if (i < 0)
      {
        type = ContentType;
      }
      else
      {
                type = ContentType.Substring(0, i);
      }
      // В зависимости от типа сообщения анализируем текст и выбираем вложения.
      switch(type)
      {
        case "multipart/mixed":
          MultipartMixed();
          break;

        case "multipart/alternative":
          MultipartMixed();
          break;

        case "multipart/related":
          MultipartMixed();
          break;

        case "text/html":
          _body = _message;
          _body_type = BodyTypes.HTML;
          break;

        case "text/plain":
          _body = _message;
          _body_type = BodyTypes.Text;
          break;
      }
    }

    /// <summary>
        /// Анализирует строку для получения e-mail.    
    /// </summary>
    /// <param name="mail">Строка с адресом</param>
    /// <returns>адрес типа [mail@host.com], [mail@localhost] или [host@123.123.123.123]</returns>
    public string ExtractEmailFromAddress(string mail)
    {
      // mail@(ip)|(host)
      Regex ex = new Regex(@"([a-zA-Z0-9_\-\.]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([a-zA-Z0-9\-]+\.?)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)");
      Match m = ex.Match(mail);
      if (!m.Success) 
      {
        throw new ParseException("email не найден.");
      }

      return m.ToString();
    }

    public void Dispose()
    {
      foreach(AttachDescriptor ad in Attachments)
      {
                ad.Close();
      }            

      _attaches = null;
      _body = null;
      _message = null;
      Headers = null;
      ChildMimes = null;

      GC.SuppressFinalize(this);
    }
    /// <summary>
    /// Перегруженный метод ToString. Возвращает информацию о сообщении.
    /// </summary>
    /// <returns></returns>
    public override string ToString()
    {
      StringBuilder sb = new StringBuilder();

      sb.AppendFormat("Size: {0}b\r\n", GetSize());
      sb.AppendFormat("From: '{0}', email: '{1}'\r\n", From, FromEmail);
      sb.AppendFormat("Subject: '{0}'\r\n", Subject);

      if (Attachments.Length > 0)
      {
        sb.Append("Attachments:\r\n");

        foreach(AttachDescriptor ad in Attachments)
        {
          sb.Append("\tName: " + ad.Name + " Size: " + ad.Size + "\r\n");
        }
      }

      return sb.ToString();
    }
    // 
    /// <summary>
    /// Возможные типы тела сообщения.
    /// </summary>
    public enum BodyTypes
    {
      Unknown,
            HTML,
      Text
    }

    /// <summary>
    /// Возвращение  тела сообщения из MIME.
    /// </summary>
    /// <param name="type">Тип тела сообщения.</param>
    /// <param name="mimes">MIME</param>
    /// <returns></returns>
    string GetBodyFromMime(BodyTypes type, Mime [] mimes)
    {
      foreach (Mime m in mimes)
      {
        if (m.ChildMimes.Length == 0)
        {
          switch(type)
          {
            case BodyTypes.HTML:
              if (m.ContentType.IndexOf("text/html") > -1)
              {
                return m.PreparedBody();
              }
              break;

            case BodyTypes.Text:
              if (m.ContentType.IndexOf("text/plain") > -1)
              {
                return m.PreparedBody();
              }
              break;
          }
        } 
        else
        {
          string r = GetBodyFromMime(type, m.ChildMimes);
          if (r != "") return r;
        }
      }

      return "";
    }

    /// <summary>
    /// Открытый метод, возвращающий тело сообщения.
    /// </summary>
    /// <param name="type">Тип тела сообщения.</param>
    /// <returns></returns>
    public string GetBody(BodyTypes type)
    {
      if (_body_type == BodyTypes.Unknown)
        return GetBodyFromMime(type, ChildMimes);
      else
        return PreparedString(_body);

    }

    /// <summary>
    /// Возвращение тела сообщения.
    /// </summary>
    /// <returns></returns>
    public string Text
    {
      get
      {
        string text;
        if (_body_type == BodyTypes.Unknown)
        {
          text = GetBodyFromMime(BodyTypes.Text, ChildMimes);
          if (text == null || text.Trim() == "")
            text = Utils.ExtractTextFromHtml(GetBodyFromMime(BodyTypes.HTML, ChildMimes));
        }
        else
        {
          text = PreparedString(_body);
                    if (_body_type == BodyTypes.HTML) 
            text = Utils.ExtractTextFromHtml(text);
        }

        return text.Trim();
      }
    }

    #region Common headers
        /// <summary>
        /// Организация.
        /// </summary>
    public string Organization
    {
      get
      {
        return (string)Headers["organization"];
      }
    }

    /// <summary>
    /// Копии письма.
    /// </summary>    
    public string CC
    {
      get
      {
        return (string)Headers["cc"];
      }
    }

    /// <summary>
    /// Дата сообщения.
    /// </summary>
    public string Date
    {
      get
      {
        return (string)Headers["date"];
      }
    }

    /// <summary>
    /// Адрес отправителя.
    /// </summary>
    public string ReturnPath
    {
      get
      {
        return (string)Headers["return-path"];
      }
    }

    /// <summary>
    /// Адрес отправителя.
    /// </summary>    
    public string From
    {
      get
      {
                return (string)Headers["from"];
      }
    }
        /// <summary>
        /// От кого.
        /// </summary>
    public string FromEmail
    {
      get
      {
        return ExtractEmailFromAddress((string)Headers["from"]);
      }
    }
        /// <summary>
        /// Кому
        /// </summary>
    public string To
    {
      get
      {
        return (string)Headers["to"];
      }
    }
        /// <summary>
        /// Тема
        /// </summary>
    public string Subject
    {
      get
      {
        return Utils.WordDecoder((string)Headers["subject"]);
      }
    }
        /// <summary>
        /// Повтор
        /// </summary>
    public string ReplyTo
    {
      get
      {
        return (string)Headers["reply-to"];
      }
    }
    #endregion
  }
}
Листинг 3.15.
Класс Mime.cs
//#define _DEBUG

namespace Mail
{
  using System;
  using System.Diagnostics;
  using System.Text;
  using System.IO;
  using System.Text.RegularExpressions;
  using System.Security.Cryptography;

  using Mail.Providers;

  public class Mime : Common
  {
    #region DEBUG
    FileStream GetDebugStream()
    {
      return new FileStream(@"C:\trace_mimes.log", FileMode.Append);
    }
    [Conditional("_DEBUG")]
    void Trace(string str)
    {
      FileStream fs = GetDebugStream();
      byte [] b = Encoding.ASCII.GetBytes(str);
      fs.Write(b, 0, b.Length);
      fs.Close();
    }
    #endregion

    internal AttachDescriptor _attach = null;

    /// <summary>
    /// Конструктор.
    /// </summary>
    /// <param name="pm">Провайдер.</param>
    /// <param name="message">Текст сообщения.</param>
    internal Mime(Provider pm, string message) : base(pm, message)
    {      
            // если MIME не содержит MultipartMixed, то осуществляется попытка  проверки на наличие вложений
      if (!MultipartMixed())
      {
        FindAttachment();
      }
    }
    /// <summary>
    /// Определение  вложения сообщения.
    /// </summary>
    void FindAttachment()
    {
      // Если вложения нет, возвращаемся назад
      if (ContentDisposition == null) 
      {
                return;
      }

      string [] cd = ContentDisposition.Split(new char [] {';'}, 2);
      switch(cd[0].ToLower())
      {
                case "attachment":
          ExtractAttachment(cd[1]);
          break;
                
        case "inline":
          throw new CoreException("не реализовано");

        default:
          throw new ParseException("Неизвестный ContentDisposition:" + cd[0]);
      }
    }
    /// <summary>
    /// Получение имени вложенного файла.
    /// </summary>
    /// <param name="filename"></param>
    /// <returns></returns>
    string GetAttachmentFilename(string filename)
    {
      Regex r = new Regex("filename=[\\\"]?([^\\r\\\"]+)");
      Match m = r.Match(filename);
      if (!m.Success)
      {
                return String.Empty;
      }      

      return Utils.WordDecoder(m.Groups[1].ToString());
    }
    /// <summary>
    /// Извлечение прикрепленных файлов из сообщения.
    /// </summary>
    /// <param name="filename">Название временного файла.</param>
    void ExtractAttachment(string filename)
    {
      _attach = new AttachDescriptor(GetAttachmentFilename(filename), _pop3.TempDirectory);

      switch (TransferEncoding)
      {
        case "base64":
          _attach.DecodeFile(_message, new FromBase64Transform());
          break;

        case "quoted-printable":
          _attach.DecodeFile(_message, new FromQuotedPrintableTransform());
          break;

        default:
                    Debug.WriteLine("Неизвестный тип кодировки.");
          break;
      }
    }

    #region Common headers
    /// <summary>
    /// Возвращение  заголовка content-disposition, сообщающего о вложении.
    /// </summary>
    string ContentDisposition
    {
      get
      {
        return (string)Headers["content-disposition"];
      }
    }
    #endregion
  }
}
Листинг 3.16.

Отправка сообщений — проект SendMail

Основной листинг MailSender.cs:
using System;
using System.Web.Mail;

namespace Mail
{
  /// <summary>
  /// Класс, отвечающий за отправку почты.
  /// </summary>
  /// <example>
  /// MailSender mailSender = new MailSender("smtp.someserver.com");
  /// MailMessage message = new MailMessage();
  /// message.From = "from@someserver.com";
  /// message.To = "to@someserver.com";
  /// message.Subject = "subject";
  /// message.Body = "body text";
  /// message.BodyFormat = MailFormat.Text;
  /// mailSender.Send(message);
  /// </example>
  public class MailSender
  {
    private string _server;
    
    /// <summary>
    /// Конструктор.
    /// </summary>
    /// <param name="server">SMTP-сервер.</param>    
    public MailSender(string server)
    {
      this._server = server;      
    }

    /// <summary>
    /// Отправка почты.
    /// </summary>
    /// <param name="message">Письмо.</param>
    public void Send(MailMessage message)
    {
      // Инициализируем сервер отправки сообщений.
      SmtpMail.SmtpServer = this._server;
      // Отправляем сообщение.
      SmtpMail.Send(message);
    }

    /// <summary>
    /// Отправка почты с паролем.
    /// </summary>
    /// <param name="message">Письмо.</param>
    /// <param name="password">Пароль пользователя.</param>
    public void Send(MailMessage message, string password)
    {
      // Добавляем к сообщению имя пользователя и пароль на тот случай,
      // когда сервер исходящей почты требует аутентификацию.
      message.Fields.Add("http://schemas.microsoft.com/cdo/configuration/smtpauthenticate", 1);
      message.Fields.Add("http://schemas.microsoft.com/cdo/configuration/sendusername", message.From); 
      message.Fields.Add("http://schemas.microsoft.com/cdo/configuration/sendpassword", password);
      // Отправляем сообщение.
      this.Send(message);
    }
  }
}
Листинг 3.17.

Интерфейс программы Ballet — проект MailApplication.

Создание новой учетной записи. Форма-контейнер Мастера

Форма CreateUserWizard представляет собой родительский контейнер для помещения в нее форм — шагов Мастера (см. рис. 3.25)

using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;

namespace MailApplication
{
  /// <summary>
  /// Summary description for CreateUserWizard.
  /// </summary>
  public class CreateUserWizard : System.Windows.Forms.Form
  {
    /// <summary>
    /// Required designer variable.
    /// </summary>
    private System.ComponentModel.Container components = null;

    public CreateUserWizard()
    {
      //
      // Required for Windows Form Designer support
      //
      InitializeComponent();

      //
      // TODO: Add any constructor code after InitializeComponent call
      //
    }

    /// <summary>
    /// Clean up any resources being used.
    /// </summary>
    protected override void Dispose( bool disposing )
    {
      if( disposing )
      {
        if(components != null)
        {
          components.Dispose();
        }
      }
      base.Dispose( disposing );
    }

    #region Windows Form Designer generated code
    /// <summary>
    /// Required method for Designer support - do not modify
    /// the contents of this method with the code editor.
    /// </summary>
    private void InitializeComponent()
    {
      System.Resources.ResourceManager resources = new System.Resources.ResourceManager(typeof(CreateUserWizard));
      // 
      // CreateUserWizard
      // 
      this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
      this.ClientSize = new System.Drawing.Size(392, 266);
      this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
      this.IsMdiContainer = true;
      this.Name = "CreateUserWizard";
      this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
      this.Text = "Создание новый учетной записи";
      this.Load += new System.EventHandler(this.CreateUserWizard_Load);

    }
    #endregion

    private void CreateUserWizard_Load(object sender, System.EventArgs e)
    {
      UserIdentity identity = new UserIdentity();      
      CUWStep1 step1 = new CUWStep1(identity);
      step1.MdiParent = this;
      step1.Show();
    }
  }
}
Листинг 3.18.
Первый шаг Мастера. Форма CUWStep1.cs

Значения свойства Name элементов управления этой формы приведены на рис. 3.27:

Форма CUWStep1

Рис. 3.27. Форма CUWStep1

Полный листинг формы:

using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;

namespace MailApplication
{
  /// <summary>
  /// Summary description for CUWStep1.
  /// </summary>
  public class CUWStep1 : System.Windows.Forms.Form
  {
    private UserIdentity identity;
    private System.Windows.Forms.Label lblEmail;
    private System.Windows.Forms.TextBox txbEmail;
    private System.Windows.Forms.Label lblMailSample;
    private System.Windows.Forms.Label lblAliasSample;
    private System.Windows.Forms.TextBox txbAlias;
    private System.Windows.Forms.Label lblAlias;
    private System.Windows.Forms.Button btnNext;
    /// <summary>
    /// Required designer variable.
    /// </summary>
    private System.ComponentModel.Container components = null;

    public CUWStep1(UserIdentity identity)
    {      
      InitializeComponent();

      this.identity = identity;
    }

    /// <summary>
    /// Clean up any resources being used.
    /// </summary>
    protected override void Dispose( bool disposing )
    {
      if( disposing )
      {
        if(components != null)
        {
          components.Dispose();
        }
      }
      base.Dispose( disposing );
    }

    #region Windows Form Designer generated code
    /// <summary>
    /// Required method for Designer support — do not modify
    /// the contents of this method with the code editor.
    /// </summary>
    private void InitializeComponent()
    {
      System.Resources.ResourceManager resources = new System.Resources.ResourceManager(typeof(CUWStep1));
      this.lblEmail = new System.Windows.Forms.Label();
      this.txbEmail = new System.Windows.Forms.TextBox();
      this.lblMailSample = new System.Windows.Forms.Label();
      this.lblAliasSample = new System.Windows.Forms.Label();
      this.txbAlias = new System.Windows.Forms.TextBox();
      this.lblAlias = new System.Windows.Forms.Label();
      this.btnNext = new System.Windows.Forms.Button();
      this.SuspendLayout();
      // 
      // lblEmail
      // 
      this.lblEmail.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 
        | System.Windows.Forms.AnchorStyles.Right)));
      this.lblEmail.Location = new System.Drawing.Point(24, 16);
      this.lblEmail.Name = "lblEmail";
      this.lblEmail.Size = new System.Drawing.Size(240, 23);
      this.lblEmail.TabIndex = 0;
      this.lblEmail.Text = "Введите адрес электронной почты";
      this.lblEmail.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
      // 
      // txbEmail
      // 
      this.txbEmail.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 
        | System.Windows.Forms.AnchorStyles.Right)));
      this.txbEmail.Location = new System.Drawing.Point(24, 48);
      this.txbEmail.Name = "txbEmail";
      this.txbEmail.Size = new System.Drawing.Size(240, 20);
      this.txbEmail.TabIndex = 1;
      this.txbEmail.Text = "";
      // 
      // lblMailSample
      // 
      this.lblMailSample.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 
        | System.Windows.Forms.AnchorStyles.Right)));
      this.lblMailSample.ForeColor = System.Drawing.SystemColors.AppWorkspace;
      this.lblMailSample.Location = new System.Drawing.Point(24, 72);
      this.lblMailSample.Name = "lblMailSample";
      this.lblMailSample.Size = new System.Drawing.Size(240, 23);
      this.lblMailSample.TabIndex = 2;
      this.lblMailSample.Text = "Например, address@mail.com";
      this.lblMailSample.TextAlign = System.Drawing.ContentAlignment.TopRight;
      // 
      // lblAliasSample
      // 
      this.lblAliasSample.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 
        | System.Windows.Forms.AnchorStyles.Right)));
      this.lblAliasSample.ForeColor = System.Drawing.SystemColors.AppWorkspace;
      this.lblAliasSample.Location = new System.Drawing.Point(26, 160);
      this.lblAliasSample.Name = "lblAliasSample";
      this.lblAliasSample.Size = new System.Drawing.Size(240, 23);
      this.lblAliasSample.TabIndex = 5;
      this.lblAliasSample.Text = "Например, Иван Васильевич";
      this.lblAliasSample.TextAlign = System.Drawing.ContentAlignment.TopRight;
      // 
      // txbAlias
      // 
      this.txbAlias.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 
        | System.Windows.Forms.AnchorStyles.Right)));
      this.txbAlias.Location = new System.Drawing.Point(26, 136);
      this.txbAlias.Name = "txbAlias";
      this.txbAlias.Size = new System.Drawing.Size(240, 20);
      this.txbAlias.TabIndex = 2;
      this.txbAlias.Text = "";
      // 
      // lblAlias
      // 
      this.lblAlias.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 
        | System.Windows.Forms.AnchorStyles.Right)));
      this.lblAlias.Location = new System.Drawing.Point(26, 104);
      this.lblAlias.Name = "lblAlias";
      this.lblAlias.Size = new System.Drawing.Size(240, 23);
      this.lblAlias.TabIndex = 3;
      this.lblAlias.Text = "Введите ваше имя ";
      this.lblAlias.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
      // 
      // btnNext
      // 
      this.btnNext.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
      this.btnNext.Location = new System.Drawing.Point(192, 192);
      this.btnNext.Name = "btnNext";
      this.btnNext.TabIndex = 3;
      this.btnNext.Text = "Далее";
      this.btnNext.Click += new System.EventHandler(this.btnNext_Click);
      // 
      // CUWStep1
      // 
      this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
      this.ClientSize = new System.Drawing.Size(292, 238);
      this.ControlBox = false;
      this.Controls.Add(this.btnNext);
      this.Controls.Add(this.lblAliasSample);
      this.Controls.Add(this.txbAlias);
      this.Controls.Add(this.txbEmail);
      this.Controls.Add(this.lblAlias);
      this.Controls.Add(this.lblMailSample);
      this.Controls.Add(this.lblEmail);
      this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
      this.Name = "CUWStep1";
      this.Text = "Шаг 1 из 3";
      this.WindowState = System.Windows.Forms.FormWindowState.Maximized;
      this.ResumeLayout(false);

    }
    #endregion

    private void btnNext_Click(object sender, System.EventArgs e)
    {
      if(txbEmail.Text == "")
      {
        MessageBox.Show("Введите адрес электронной почты.");
        return;
      }
      else
      {
        identity.Alias = txbAlias.Text;
        identity.Mail = txbEmail.Text;
        
        CUWStep2 step2 = new CUWStep2(this.identity);
        step2.MdiParent = this.MdiParent;
        this.Close();
        step2.Show();
      }
    }

    
  }
}
Листинг 3.19.
Второй шаг Мастера. Форма CUWStep2.cs

Значения свойства Name элементов управления этой формы приведены на рис. 3.28.

Форма CUWStep2

Рис. 3.28. Форма CUWStep2

Полный листинг формы:

using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;

namespace MailApplication
{
  /// <summary>
  /// Summary description for CUWStep2.
  /// </summary>
  public class CUWStep2 : System.Windows.Forms.Form
  {
    private UserIdentity identity;
    private System.Windows.Forms.Label lblPop3Sample;
    private System.Windows.Forms.TextBox txbPop3;
    private System.Windows.Forms.Label lblPop3;
    private System.Windows.Forms.Label lblPop3PortSample;
    private System.Windows.Forms.TextBox txbPop3Port;
    private System.Windows.Forms.Label lblPop3Port;
    private System.Windows.Forms.Button btnNext;
    /// <summary>
    /// Required designer variable.
    /// </summary>
    private System.ComponentModel.Container components = null;

    public CUWStep2(UserIdentity identity)
    {      
      InitializeComponent();

      this.identity = identity;
    }

    /// <summary>
    /// Clean up any resources being used.
    /// </summary>
    protected override void Dispose( bool disposing )
    {
      if( disposing )
      {
        if(components != null)
        {
          components.Dispose();
        }
      }
      base.Dispose( disposing );
    }

    #region Windows Form Designer generated code
    /// <summary>
    /// Required method for Designer support — do not modify
    /// the contents of this method with the code editor.
    /// </summary>
    private void InitializeComponent()
    {
      System.Resources.ResourceManager resources = new System.Resources.ResourceManager(typeof(CUWStep2));
      this.lblPop3Sample = new System.Windows.Forms.Label();
      this.txbPop3 = new System.Windows.Forms.TextBox();
      this.lblPop3 = new System.Windows.Forms.Label();
      this.lblPop3PortSample = new System.Windows.Forms.Label();
      this.txbPop3Port = new System.Windows.Forms.TextBox();
      this.lblPop3Port = new System.Windows.Forms.Label();
      this.btnNext = new System.Windows.Forms.Button();
      this.SuspendLayout();
      // 
      // lblPop3Sample
      // 
      this.lblPop3Sample.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 
        | System.Windows.Forms.AnchorStyles.Right)));
      this.lblPop3Sample.ForeColor = System.Drawing.SystemColors.AppWorkspace;
      this.lblPop3Sample.Location = new System.Drawing.Point(26, 64);
      this.lblPop3Sample.Name = "lblPop3Sample";
      this.lblPop3Sample.Size = new System.Drawing.Size(240, 23);
      this.lblPop3Sample.TabIndex = 5;
      this.lblPop3Sample.Text = "Например, pop3.mail.com";
      this.lblPop3Sample.TextAlign = System.Drawing.ContentAlignment.TopRight;
      // 
      // txbPop3
      // 
      this.txbPop3.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 
        | System.Windows.Forms.AnchorStyles.Right)));
      this.txbPop3.Location = new System.Drawing.Point(26, 40);
      this.txbPop3.Name = "txbPop3";
      this.txbPop3.Size = new System.Drawing.Size(240, 20);
      this.txbPop3.TabIndex = 4;
      this.txbPop3.Text = "";
      // 
      // lblPop3
      // 
      this.lblPop3.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 
        | System.Windows.Forms.AnchorStyles.Right)));
      this.lblPop3.Location = new System.Drawing.Point(26, 8);
      this.lblPop3.Name = "lblPop3";
      this.lblPop3.Size = new System.Drawing.Size(240, 23);
      this.lblPop3.TabIndex = 3;
      this.lblPop3.Text = "Введите адрес  сервера POP3:";
      this.lblPop3.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
      // 
      // lblPop3PortSample
      // 
      this.lblPop3PortSample.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 
        | System.Windows.Forms.AnchorStyles.Right)));
      this.lblPop3PortSample.ForeColor = System.Drawing.SystemColors.AppWorkspace;
      this.lblPop3PortSample.Location = new System.Drawing.Point(26, 160);
      this.lblPop3PortSample.Name = "lblPop3PortSample";
      this.lblPop3PortSample.Size = new System.Drawing.Size(240, 23);
      this.lblPop3PortSample.TabIndex = 8;
      this.lblPop3PortSample.Text = "Например, 110";
      this.lblPop3PortSample.TextAlign = System.Drawing.ContentAlignment.TopRight;
      // 
      // txbPop3Port
      // 
      this.txbPop3Port.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 
        | System.Windows.Forms.AnchorStyles.Right)));
      this.txbPop3Port.Location = new System.Drawing.Point(26, 136);
      this.txbPop3Port.Name = "txbPop3Port";
      this.txbPop3Port.Size = new System.Drawing.Size(240, 20);
      this.txbPop3Port.TabIndex = 7;
      this.txbPop3Port.Text = "110";
      // 
      // lblPop3Port
      // 
      this.lblPop3Port.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 
        | System.Windows.Forms.AnchorStyles.Right)));
      this.lblPop3Port.Location = new System.Drawing.Point(26, 104);
      this.lblPop3Port.Name = "lblPop3Port";
      this.lblPop3Port.Size = new System.Drawing.Size(240, 23);
      this.lblPop3Port.TabIndex = 6;
      this.lblPop3Port.Text = "Укажите почтовый порт:";
      this.lblPop3Port.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
      // 
      // btnNext
      // 
      this.btnNext.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
      this.btnNext.Location = new System.Drawing.Point(192, 200);
      this.btnNext.Name = "btnNext";
      this.btnNext.TabIndex = 9;
      this.btnNext.Text = "Далее";
      this.btnNext.Click += new System.EventHandler(this.btnNext_Click);
      // 
      // CUWStep2
      // 
      this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
      this.ClientSize = new System.Drawing.Size(292, 238);
      this.Controls.Add(this.btnNext);
      this.Controls.Add(this.lblPop3PortSample);
      this.Controls.Add(this.txbPop3Port);
      this.Controls.Add(this.lblPop3Port);
      this.Controls.Add(this.lblPop3Sample);
      this.Controls.Add(this.txbPop3);
      this.Controls.Add(this.lblPop3);
      this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
      this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
      this.Name = "CUWStep2";
      this.Text = "Шаг 2 из 3";
      this.WindowState = System.Windows.Forms.FormWindowState.Maximized;
      this.ResumeLayout(false);

    }
    #endregion

    private void btnNext_Click(object sender, System.EventArgs e)
    {
      if(txbPop3.Text == "")
      {
        MessageBox.Show("Введите адрес сервера POP3");
      }
      else
      {
        this.identity.Pop3 = txbPop3.Text;
        try
        {
          //Преобразовываем введенное значение в тип Int32 
          this.identity.Pop3Port = Int32.Parse(txbPop3Port.Text);
          CUWStep3 step3 = new CUWStep3(this.identity);
          step3.MdiParent = this.MdiParent;
          this.Close();
          step3.Show();
        }
        catch(Exception)
        {
          MessageBox.Show("Значение порта должно быть числом");
        }
      }

    }
  }
}
Листинг 3.20.
Третий шаг Мастера. Форма CUWStep3.cs

Значения свойства Name элементов управления этой формы приведены на рис. 3.29.

Форма CUWStep3

Рис. 3.29. Форма CUWStep3

Полный листинг формы:

using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Threading;
using System.Security.Principal;

namespace MailApplication
{
  /// <summary>
  /// Summary description for CUWStep3.
  /// </summary>
  public class CUWStep3 : System.Windows.Forms.Form
  {
    private UserIdentity identity;
    private System.Windows.Forms.Label lblSmtpSample;
    private System.Windows.Forms.TextBox txbSmtp;
    private System.Windows.Forms.Label lblSmtp;
    private System.Windows.Forms.Button btnFinish;
    /// <summary>
    /// Required designer variable.
    /// </summary>
    private System.ComponentModel.Container components = null;

    public CUWStep3(UserIdentity identity)
    {
      InitializeComponent();
      this.identity = identity;
    }

    /// <summary>
    /// Clean up any resources being used.
    /// </summary>
    protected override void Dispose( bool disposing )
    {
      if( disposing )
      {
        if(components != null)
        {
          components.Dispose();
        }
      }
      base.Dispose( disposing );
    }

    #region Windows Form Designer generated code
    /// <summary>
    /// Required method for Designer support — do not modify
    /// the contents of this method with the code editor.
    /// </summary>
    private void InitializeComponent()
    {
      System.Resources.ResourceManager resources = new System.Resources.ResourceManager(typeof(CUWStep3));
      this.lblSmtpSample = new System.Windows.Forms.Label();
      this.txbSmtp = new System.Windows.Forms.TextBox();
      this.lblSmtp = new System.Windows.Forms.Label();
      this.btnFinish = new System.Windows.Forms.Button();
      this.SuspendLayout();
      // 
      // lblSmtpSample
      // 
      this.lblSmtpSample.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 
        | System.Windows.Forms.AnchorStyles.Right)));
      this.lblSmtpSample.ForeColor = System.Drawing.SystemColors.AppWorkspace;
      this.lblSmtpSample.Location = new System.Drawing.Point(26, 72);
      this.lblSmtpSample.Name = "lblSmtpSample";
      this.lblSmtpSample.Size = new System.Drawing.Size(240, 23);
      this.lblSmtpSample.TabIndex = 8;
      this.lblSmtpSample.Text = "Например, smtp.mail.com";
      this.lblSmtpSample.TextAlign = System.Drawing.ContentAlignment.TopRight;
      // 
      // txbSmtp
      // 
      this.txbSmtp.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 
        | System.Windows.Forms.AnchorStyles.Right)));
      this.txbSmtp.Location = new System.Drawing.Point(26, 48);
      this.txbSmtp.Name = "txbSmtp";
      this.txbSmtp.Size = new System.Drawing.Size(240, 20);
      this.txbSmtp.TabIndex = 7;
      this.txbSmtp.Text = "";
      // 
      // lblSmtp
      // 
      this.lblSmtp.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 
        | System.Windows.Forms.AnchorStyles.Right)));
      this.lblSmtp.Location = new System.Drawing.Point(26, 16);
      this.lblSmtp.Name = "lblSmtp";
      this.lblSmtp.Size = new System.Drawing.Size(240, 23);
      this.lblSmtp.TabIndex = 6;
      this.lblSmtp.Text = "Введите адрес SMTP-сервера:";
      this.lblSmtp.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
      // 
      // btnFinish
      // 
      this.btnFinish.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
      this.btnFinish.Location = new System.Drawing.Point(200, 208);
      this.btnFinish.Name = "btnFinish";
      this.btnFinish.TabIndex = 9;
      this.btnFinish.Text = "Готово";
      this.btnFinish.Click += new System.EventHandler(this.btnFinish_Click);
      // 
      // CUWStep3
      // 
      this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
      this.ClientSize = new System.Drawing.Size(292, 266);
      this.Controls.Add(this.btnFinish);
      this.Controls.Add(this.lblSmtpSample);
      this.Controls.Add(this.txbSmtp);
      this.Controls.Add(this.lblSmtp);
      this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
      this.Name = "CUWStep3";
      this.Text = "Шаг 3 из 3";
      this.WindowState = System.Windows.Forms.FormWindowState.Maximized;
      this.ResumeLayout(false);

    }
    #endregion

    private void btnFinish_Click(object sender, System.EventArgs e)
    {
      if(txbSmtp.Text != "")
      {
        this.identity.Smtp = txbSmtp.Text;
        //Закрываем текущую форму
        this.Close();
        Thread.CurrentPrincipal = new GenericPrincipal(this.identity, new string[]{"user"});
        this.identity.Dispose();
        //Закрываем родительскую форму CreateUserWizard  
        Form.ActiveForm.Close();
        
      }
      else
      {
        MessageBox.Show("Введите адрес сервера SMTP");
      }
    }

  
  }
}
Листинг 3.21.
Главная форма mainForm.cs

Главная форма программы представляет собой контейнер для других форм и поэтому содержит сравнительно мало элементов управления. Значения свойства Name элементов управления приведены на рис. 3.30.

Форма mainForm

Рис. 3.30. Форма mainForm

Полный листинг формы:

using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using System.Threading;
using System.Security.Principal;

namespace MailApplication
{
  /// <summary>
  /// Summary description for Form1.
  /// </summary>
  public class mainForm : System.Windows.Forms.Form
  {
    private System.Windows.Forms.MainMenu mainMenu;
    private System.Windows.Forms.MenuItem itemFile;
    private System.Windows.Forms.MenuItem itemUsers;
    private System.Windows.Forms.MenuItem itemNewUser;
    private System.Windows.Forms.MenuItem itemExit;
    private System.Windows.Forms.MenuItem itemEvent;
    private System.Windows.Forms.MenuItem itemGet;
    private System.Windows.Forms.MenuItem itemSend;
    private System.Windows.Forms.MenuItem itemSetting;
    /// <summary>
    /// Required designer variable.
    /// </summary>
    private System.ComponentModel.Container components = null;

    public mainForm()
    {
      //
      // Required for Windows Form Designer support
      //
      InitializeComponent();

      //
      // TODO: Add any constructor code after InitializeComponent call
      //
    }

    /// <summary>
    /// Clean up any resources being used.
    /// </summary>
    protected override void Dispose( bool disposing )
    {
      if( disposing )
      {
        if (components != null) 
        {
          components.Dispose();
        }
      }
      base.Dispose( disposing );
    }

    #region Windows Form Designer generated code
    /// <summary>
    /// Required method for Designer support — do not modify
    /// the contents of this method with the code editor.
    /// </summary>
    private void InitializeComponent()
    {
      System.Resources.ResourceManager resources = new System.Resources.ResourceManager(typeof(mainForm));
      this.mainMenu = new System.Windows.Forms.MainMenu();
      this.itemFile = new System.Windows.Forms.MenuItem();
      this.itemNewUser = new System.Windows.Forms.MenuItem();
      this.itemUsers = new System.Windows.Forms.MenuItem();
      this.itemExit = new System.Windows.Forms.MenuItem();
      this.itemEvent = new System.Windows.Forms.MenuItem();
      this.itemGet = new System.Windows.Forms.MenuItem();
      this.itemSend = new System.Windows.Forms.MenuItem();
      this.itemSetting = new System.Windows.Forms.MenuItem();
      // 
      // mainMenu
      // 
      this.mainMenu.MenuItems.AddRange(new System.Windows.Forms.MenuItem[] {
                                           this.itemFile,
                                           this.itemEvent});
      // 
      // itemFile
      // 
      this.itemFile.Index = 0;
      this.itemFile.MenuItems.AddRange(new System.Windows.Forms.MenuItem[] {
                                           this.itemNewUser,
                                           this.itemUsers,
                                           this.itemExit});
      this.itemFile.Text = "Файл";
      // 
      // itemNewUser
      // 
      this.itemNewUser.Index = 0;
      this.itemNewUser.Shortcut = System.Windows.Forms.Shortcut.CtrlN;
      this.itemNewUser.Text = "Новый пользователь";
      this.itemNewUser.Click += new System.EventHandler(this.itemNewUser_Click);
      // 
      // itemUsers
      // 
      this.itemUsers.Index = 1;
      this.itemUsers.Shortcut = System.Windows.Forms.Shortcut.CtrlL;
      this.itemUsers.Text = "Смена пользователя";
      this.itemUsers.Click += new System.EventHandler(this.itemUsers_Click);
      // 
      // itemExit
      // 
      this.itemExit.Index = 2;
      this.itemExit.Shortcut = System.Windows.Forms.Shortcut.AltF4;
      this.itemExit.Text = "Выход";
      this.itemExit.Click += new System.EventHandler(this.itemExit_Click);
      // 
      // itemEvent
      // 
      this.itemEvent.Enabled = false;
      this.itemEvent.Index = 1;
      this.itemEvent.MenuItems.AddRange(new System.Windows.Forms.MenuItem[] {
                                            this.itemGet,
                                            this.itemSend,
                                            this.itemSetting});
      this.itemEvent.Text = "Действия";
      // 
      // itemGet
      // 
      this.itemGet.Index = 0;
      this.itemGet.Shortcut = System.Windows.Forms.Shortcut.CtrlG;
      this.itemGet.Text = "Получить почту";
      this.itemGet.Click += new System.EventHandler(this.itemGet_Click);
      // 
      // itemSend
      // 
      this.itemSend.Index = 1;
      this.itemSend.Shortcut = System.Windows.Forms.Shortcut.CtrlS;
      this.itemSend.Text = "Отправить письмо";
      this.itemSend.Click += new System.EventHandler(this.itemSend_Click);
      // 
      // itemSetting
      // 
      this.itemSetting.Index = 2;
      this.itemSetting.Shortcut = System.Windows.Forms.Shortcut.CtrlO;
      this.itemSetting.Text = "Настройки";
      this.itemSetting.Visible = false;
      // 
      // mainForm
      // 
      this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
      this.ClientSize = new System.Drawing.Size(792, 545);
      this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
      this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
      this.IsMdiContainer = true;
      this.Menu = this.mainMenu;
      this.Name = "mainForm";
      this.Text = "Ballet";
      this.Closing += new System.ComponentModel.CancelEventHandler(this.mainForm_Closing);
      this.Load += new System.EventHandler(this.itemUsers_Click);

    }
    #endregion

    /// <summary>
    /// The main entry point for the application.
    /// </summary>
    [STAThread]
    static void Main() 
    {
      Application.Run(new mainForm());
    }

    private void itemExit_Click(object sender, System.EventArgs e)
    {
      this.Close();
    }

    private void itemUsers_Click(object sender, System.EventArgs e)
    {
      selectUser select = new selectUser();
      if(select.ShowDialog() != DialogResult.OK)
        //Запускаем главную форму.
        return;
      if(Thread.CurrentPrincipal.Identity is UserIdentity)
        ((UserIdentity)Thread.CurrentPrincipal.Identity).Dispose();
      string userName = select.lstViewUsers.SelectedItems[0].Text;      
      UserIdentity identity = new UserIdentity(userName);      
      Thread.CurrentPrincipal = new GenericPrincipal(identity, new string[]{"user"});
      //Вызываем метод ActivateEventItem
      this.ActivateEventItem();
    }

    private void ActivateEventItem()
    {
      //Включаем доступность пункта меню "Действия".
      this.itemEvent.Enabled = true;
    }

    private void itemNewUser_Click(object sender, System.EventArgs e)
    {
      //Создаем экземпляр wizard формы CreateUserWizard
      CreateUserWizard wizard = new CreateUserWizard();
      //Показываем форму:
      wizard.ShowDialog();
      if(Thread.CurrentPrincipal != null)
        this.ActivateEventItem();
    }

    private void mainForm_Closing(object sender, System.ComponentModel.CancelEventArgs e)
    {
      if(Thread.CurrentPrincipal.Identity is UserIdentity)
        ((UserIdentity)Thread.CurrentPrincipal.Identity).Dispose();
    }

    private void itemSend_Click(object sender, System.EventArgs e)
    {      
      PasswordPromt pass = new PasswordPromt();
      if(pass.ShowDialog() != DialogResult.OK)
        return;
      SendMessage send = new SendMessage();
      send.MdiParent = this;
      send.Show();
    }

    private void itemGet_Click(object sender, System.EventArgs e)
    {
      PasswordPromt pass = new PasswordPromt();
      if(pass.ShowDialog() != DialogResult.OK)
        return;
      MessageList list = new MessageList();
      list.MdiParent = this;
      list.Show();
    }

    

    
  }
}
Листинг 3.22.
Елена Дьяконова
Елена Дьяконова

При нажатии на Сумма в примере ArbitraryMethod из Лекция 7, VS 2013 выдается ошибка: 

Необработанное исключение типа "System.InvalidOperationException" в System.Windows.Forms.dll

Дополнительные сведения: Недопустимая операция в нескольких потоках: попытка доступа к элементу управления "lblResult" не из того потока, в котором он был создан.

Затем:

Необработанное исключение типа "System.InvalidOperationException" в mscorlib.dll

Дополнительные сведения: Для каждой асинхронной операции метод EndInvoke может вызываться только один раз.

Александр Сороколет
Александр Сороколет

Свойство WindowState формы blank Maximized. Не открывается почемуто на всё окно, а вот если последующую форму бланк открыть уже на макс открывается :-/