В данной статье рассмотрим рефакторинг приложения на основе паттернов, а именно - использование паттерна Factory
Method (фабричный метод).
Паттерны - мощный инструмент, который может помочь привести код
к одному стилю. Такой код можно легко модифицировать и сопровождать. Правда, иногда неумелое использование паттернов может усложнить жизнь разработчикам. Но
в большинстве случаев они выступают как спасательный круг в море кода. Недавно
довелось услышать интересное высказывание на данный счет: "Вы
когда-нибудь бросали чтение классической книги после прочтения двух десятков
страниц, толком не понимая, почему же ее так хвалят и что же со мной не так?
При этом я не говорю за книги, типа банды
четырех, о которой говорят на каждом шагу, но при
этом есть лишь четыре человека в мире, которые прочитали ее от начала до конца."
С такой фразы начинается статья "О дизайне и сложностях перевода", которая описывает
сложности, с которыми столкнётся читатель при чтении книг по
проектированию корпоративных решений. Например, книга "DDD"
Эванса интересная, но довольно сложная для понимания, так как некоторые моменты из неё ставят меня в
тупик до сих пор. Соглашусь с мнением автора о том, что о
данных книгах говорят всюду, но их прочитали только несколько человек, и те, вероятно, вскоре забыли прочитанный материал. На каждом проекте, в котором принимают участие
разработчики разного уровня знаний, как правило, понимают, зачем нужны паттерны
и как правильно их применять, лишь несколько человек. Иногда есть и другие моменты,
когда человек использует паттерн, но не знает его названия и не может
структурировать мысли по его поводу. Эта статья покажет возможность того, как паттерны упрощают жизнь
разработчику программного обеспечения. Пример для данной статьи написан на
основе кода, взятого с реального коммерческого проекта, но по максимуму упрощен
для использования. Задача этого кода - показать, как был
написан основной вариант кода, и как этот код можно изменить. Для примера представлен прототип кода для
отправки сообщений на базе компонента MailBee.NET. Интерфейс для отправки и получения сообщений:
public interface ICommunicator
{
void Send(string from, string to, string[] relatedFiles, IEmailServerSettings settings);
void
Recieve();
}
Интерфейс
для работы с почтой:
public interface IEmailCommunicator
{
/// <summary>
/// Получить идентификаторы всех имеющихся на сервере сообщений
/// </summary>
string[] GetMessageUids(IEmailServerSettings serverSettings);
/// <summary>
/// Отправка e-mail
/// </summary>
/// <param
name="from">Поле "От" в e-mail</param>
/// <param name="to">Поле "Кому" в e-mail</param>
/// <param
name="relatedFiles">Вложения</param>
/// <param
name="settings">SMTP настройки</param>
void Send(string from,
string to,
string[] relatedFiles,
IEmailServerSettings settings);
/// <summary>
/// Возвращает
список имен файлов, которые были загружены с ошибкой
/// </summary>
/// <param
name="notProcessedUids">Идентификаторы
необработанных сообщений</param>
/// <param name="serverSettings">Объект параметров сервера</param>
/// <param
name="directoryToSave">Папка для сохранения вложений</param>
/// <param
name="isNeedToDeleteMessageAfterReceive">Признак необходимости удаления письма после получения</param>
/// <returns>Список имен
файлов, полученных с ошибкой</returns>
List<string> GetEmails(string[] notProcessedUids,
IEmailServerSettings serverSettings,
string directoryToSave,
bool isNeedToDeleteMessageAfterReceive);
/// <summary>
/// Проверка аутентификации на на потовом сервере
/// </summary>
/// <param
name="serverSettings">Параметры сервера</param>
bool
ServerAuthentication(IEmailServerSettings serverSettings);
}
Интерфейс
для настройки почтового сервера:
public interface IEmailServerSettings
{
/// <summary>
/// Сервер
/// </summary>
string Address { get; set; }
/// <summary>
/// Порт
/// </summary>
int Port { get; set; }
/// <summary>
/// Имя пользователя
/// </summary>
string UserName { get; set; }
/// <summary>
/// Пароль пользователя
/// </summary>
string UserPassword { get; set; }
/// <summary>
/// Признак необходимости использовать SSL
/// </summary>
bool UseSsl { get; set; }
/// <summary>
/// Тип сервера
настроек электронной почты
/// </summary>
EmailServerType EmailServerType { get; set; }
}
Рассмотрим саму отправку и прием писем с помощью данной библиотеки.
public class EmailCommunicator : ICommunicator, IEmailCommunicator
{
#region [
private methods ]
private static void CheckSmtpServerParams(IEmailServerSettings smtpServerParams)
{
if (smtpServerParams == null)
throw new ArgumentNullException("smtpServerParams");
if (smtpServerParams.EmailServerType
!= EmailServerType.Smtp)
throw new ArgumentException("smtpServerParams has
different type from EmailServerType.Smtp");
}
private static void CheckPop3ServerParams(IEmailServerSettings pop3ServerParams)
{
if (pop3ServerParams == null)
throw new ArgumentNullException("pop3ServerParams");
if (pop3ServerParams.EmailServerType
!= EmailServerType.Pop3)
throw new ArgumentException("pop3ServerParams has
different type from EmailServerType.Pop3");
}
private static void SmtpServerAuthenticationInternal(Smtp mailer, IEmailServerSettings smtpServerSettings)
{
if (mailer == null)
throw new ArgumentNullException("mailer");
CheckSmtpServerParams(smtpServerSettings);
int portInteger =
smtpServerSettings.Port;
// Создаем
объект для указания параметров SMTP-сервера
//
var server = new SmtpServer
{
Name = smtpServerSettings.Address,
AuthMethods = string.IsNullOrEmpty(smtpServerSettings.UserName)
? AuthenticationMethods.None
: AuthenticationMethods.Auto,
AuthOptions = AuthenticationOptions.PreferSimpleMethods,
AccountName =
smtpServerSettings.UserName,
Port = portInteger,
Password =
smtpServerSettings.UserPassword
};
//
// Использование SSL
//
if (smtpServerSettings.UseSsl)
server.SslMode = SslStartupMode.OnConnect;
//
// Добавляем параметры SMTP-сервера
//
mailer.DnsServers.Clear();
mailer.SmtpServers.Clear();
mailer.SmtpServers.Add(server);
//
// Аутентификация
на SMTP-сервере
//
if (!mailer.IsConnected)
mailer.Connect();
mailer.Hello();
if (mailer.IsLoggedIn)
return;
var loginSuccess = mailer.Login();
if (!loginSuccess)
throw new Exception("Login error");
}
private static void Pop3ServerAuthAndLoginInternal(Pop3 pop3, IEmailServerSettings pop3ServerSettings)
{
if (pop3 == null)
throw new ArgumentNullException("pop3");
CheckPop3ServerParams(pop3ServerSettings);
int portInteger =
pop3ServerSettings.Port;
if (pop3ServerSettings.UseSsl)
pop3.SslMode = SslStartupMode.OnConnect;
//
// Соединяемся
с POP3-сервером и проводим аутентификацию
//
if (!pop3.IsConnected)
pop3.Connect(pop3ServerSettings.Address, portInteger);
if (!pop3.IsLoggedIn)
pop3.Login(pop3ServerSettings.UserName,
pop3ServerSettings.UserPassword,
AuthenticationMethods.Auto,
AuthenticationOptions.PreferSimpleMethods,
null);
}
private static bool IsHeaderCorrect(MailMessage header)
{
return header != null &&
!string.IsNullOrEmpty((string)header.UidOnServer);
}
/// <summary>
/// Проверка аутентификации на SMTP-сервере
/// </summary>
/// <param
name="smtpServerSettings">Объект параметров SMTP-сервера</param>
private static bool SmtpServerAuthentication(IEmailServerSettings smtpServerSettings)
{
CheckSmtpServerParams(smtpServerSettings);
using (var mailer = new Smtp())
{
try
{
SmtpServerAuthenticationInternal(mailer, smtpServerSettings);
}
catch (Exception e)
{
//Log Exception
return false;
}
finally
{
try
{
if (mailer.IsConnected)
mailer.Disconnect();
}
// ReSharper disable
EmptyGeneralCatchClause
catch
// ReSharper restore
EmptyGeneralCatchClause
{
// Обработка исключения пропущена поскольку не корректная работа
метода Disconnect()
// сделана на всякий случай не влияет на логику программы
}
}
}
return true;
}
/// <summary>
/// Проверка аутентификации на POP3-сервере
/// </summary>
/// <param
name="pop3ServerSettings">Объект параметров РОР3-сервера</param>
private static bool Pop3ServerAuthentication(IEmailServerSettings pop3ServerSettings)
{
CheckPop3ServerParams(pop3ServerSettings);
using (var pop3 = new Pop3())
{
try
{
Pop3ServerAuthAndLoginInternal(pop3, pop3ServerSettings);
}
catch (Exception e)
{
//WriteErrorLog(pop3.Log, e);
return false;
}
finally
{
try
{
if (pop3.IsConnected)
pop3.Disconnect();
}
// ReSharper disable
EmptyGeneralCatchClause
catch
// ReSharper restore
EmptyGeneralCatchClause
{
// Обработка исключения пропущена поскольку не корректная работа метода Disconnect()
// сделана на всякий случай не влияет на логику программы
}
}
}
return true;
}
#endregion
#region [
public methods ]
/// <summary>
/// Получить идентификаторы всех имеющихся на сервере сообщений
/// </summary>
public string[] GetMessageUids(IEmailServerSettings pop3ServerSettings)
{
CheckPop3ServerParams(pop3ServerSettings);
using (var pop3 = new Pop3())
{
pop3.InboxPreloadOptions = Pop3InboxPreloadOptions.Uidl;
Pop3ServerAuthAndLoginInternal(pop3, pop3ServerSettings);
string[] uids;
try
{
uids = pop3.GetMessageUids();
}
finally
{
try
{
if (pop3.IsConnected)
pop3.Disconnect();
}
// ReSharper disable
EmptyGeneralCatchClause
catch
// ReSharper restore EmptyGeneralCatchClause
{
// Обработка исключения пропущена поскольку не корректная работа метода Disconnect()
// сделана на всякий случай не влияет на логику программы
}
}
return uids;
}
}
/// <summary>
/// Отправка e-mail
/// </summary>
/// <param
name="from">Поле "От" в e-mail</param>
/// <param name="to">Поле "Кому" в e-mail</param>
/// <param
name="relatedFiles">Вложения</param>
/// <param
name="settings">SMTP настройки</param>
public void Send(string from,
string to,
string[] relatedFiles,
IEmailServerSettings settings)
{
using (var mailer = new Smtp())
{
try
{
SmtpServerAuthenticationInternal(mailer, settings);
//
// Указываем параметры сообщения
//
mailer.From.AsString = String.IsNullOrEmpty(from)
? "default"
: from;
mailer.To.AsString = to;
mailer.Subject
= string.Empty;//заголовки не реализованы
mailer.BodyPlainText = string.Empty;//тело письма не реализовано
foreach (var file in relatedFiles)
{
mailer.AddAttachment(file);
}
//
// Отправляем
//
mailer.Send();
}
finally
{
try
{
if (mailer.IsConnected)
mailer.Disconnect();
}
// ReSharper disable
EmptyGeneralCatchClause
catch
// ReSharper restore
EmptyGeneralCatchClause
{
// Обработка исключения пропущена поскольку не корректная работа метода Disconnect()
// сделана на всякий случай не влияет на логику программы
}
}
}
}
public void Recieve()
{
throw new NotImplementedException();
}
/// <summary>
/// Возвращает список имен файлов, которые были загружены с ошибкой
/// </summary>
/// <param
name="notProcessedUids">Идентификаторы
необработанных сообщений</param>
/// <param name="serverSettings">Объект параметров сервера</param>
/// <param
name="isNeedToDeleteMessageAfterReceive">Признак необходимости удаления письма после получения</param>
/// <returns>Список имен файлов, полученных с ошибкой</returns>
public List<string> GetEmails(string[] notProcessedUids,
IEmailServerSettings serverSettings,
string directoryToSave,
bool
isNeedToDeleteMessageAfterReceive)
{
CheckPop3ServerParams(serverSettings);
using (var pop3 = new Pop3())
{
try
{
pop3.InboxPreloadOptions = Pop3InboxPreloadOptions.Uidl;
//
//
Список имен файлов, которые были загруженны с ошибкой
//
var result = new List<string>();
//
//
Создаем временную папку для сохранения квитанций
//
try
{
if (!Directory.Exists(directoryToSave))
Directory.CreateDirectory(directoryToSave);
}
catch (Exception ex)
{
throw new Exception("Create save ticket
directory",
ex);
}
Pop3ServerAuthAndLoginInternal(pop3, serverSettings);
//
// Получаем список уже прочитанных писем на РОР3-сервере для "отсеивания" новых писем
//
foreach (var uid in notProcessedUids)
{
bool? isHandledTicketEmailMessage = null;
var messageIndex =
pop3.GetMessageIndexFromUid(uid);
var messageHeader =
pop3.DownloadMessageHeader(messageIndex);
if (IsHeaderCorrect(messageHeader))
{
//
// Загружаем сообщение
//
var mailMessage =
pop3.DownloadEntireMessage(messageIndex);
//
// Просматриваем и сохраняем приложения письма
//
foreach (var attachment in
mailMessage.Attachments.OfType<Attachment>())
{
//
// Записываем файл вложения в directoryToSave
//
try
{
var uniqueFileName =
attachment.FilenameOriginal;
File.WriteAllBytes(uniqueFileName,
attachment.GetData());
//
// Если при записи предыдущих вложений не возникало ошибок,
то помечаем письмо к удалению
//
if (isHandledTicketEmailMessage == null)
isHandledTicketEmailMessage = true;
}
catch
{
//Если при получении возникла ошибка, то записываем имя файла
в результат - как ошибочный
result.Add(attachment.Filename);
isHandledTicketEmailMessage = false;
}
}
}
//
// Удаляем письмо с POP3-сервера, если указан признак необходимости
// удаления письма после получения и в письме содержится подходяшее
приложение
//
try
{
if
(isNeedToDeleteMessageAfterReceive
&&
isHandledTicketEmailMessage.HasValue
&&
isHandledTicketEmailMessage.Value)
{
pop3.DeleteMessage(messageIndex);
}
}
// ReSharper disable
EmptyGeneralCatchClause
catch
// ReSharper restore
EmptyGeneralCatchClause
{
// Ошибку удаления письма пользователю не показываем
}
}
return result;
}
finally
{
try
{
if (pop3.IsConnected)
pop3.Disconnect();
}
// ReSharper disable
EmptyGeneralCatchClause
catch
// ReSharper restore
EmptyGeneralCatchClause
{
// Обработка исключения пропущена поскольку не корректная работа метода Disconnect()
// сделана на всякий случай не влияет на логику программы
}
}
}
}
/// <summary>
/// Проверка аутентификации на на потовом сервере
/// </summary>
/// <param
name="serverSettings">Параметры сервера</param>
public bool ServerAuthentication(IEmailServerSettings serverSettings)
{
switch (serverSettings.EmailServerType)
{
case EmailServerType.Smtp:
{
return
SmtpServerAuthentication(serverSettings);
}
case EmailServerType.Pop3:
{
return
Pop3ServerAuthentication(serverSettings);
}
default:
return false;
}
}
#endregion
}
Код
получился довольно большой и плохо расширяемый. За задумкой разработчика
данного творения, этот класс можно легко расширять и использовать в дальнейшем.
Моя задача для такого кода состояла в том, чтобы добавить поддержку протокола IMAP для входящих сообщений. Первый вариант, как я это реализовал, - добавил в данный класс функции, необходимые для
реализации протокола IMAP для данного класса:
- CheckImapServerParams;
- ImapServerAuthAndLoginInternal;
- ImapServerAuthentication.
Посмотрим на данный код с другой стороны. Что делать, если в наше приложение
добавить поддержку ещё некоторого протокола для почты, снова дублировать код? Если внимательно посмотреть на код, то с первого раза в нём видно очень
много дублирования. Есть несколько способов решить данную проблему. Два способа,
которые пришли мне в голову сразу, как только я увидел данный код:
- Отделить код по отправке и получения почты по разным интерфейсам. Для нашего примера их будет 2: IReciever и ISender.
- Сделать один общий интерфейс IMailCommunicator в котором объединить логику.
Первый
вариант более изящный, но плодит больше
логики. Второй вариант проще, так как у нас интерфейс для отправки, по сути, только один (SMTP), то метод для отправки сообщения мы можем добавить в общий интерфейс. Я
выбрал второй вариант потому, что я довольно ленив, и также из-за того, что
этот вариант можно реализовать с помощью фабричного метода. Рассмотрим описание
данного паттерна с книги банды четырех. Фабричный метод – паттерн, порождающий классы.
Назначение: определяет
интерфейс для создания объекта, но оставляет подклассам решение о том, какой
класс инстанцировать. Фабричный метод позволяет классу делегировать
инстанцирование подклассам. Другими
словами, мы передаем в метод некоторые параметры, а он на основании этих параметров создаст нам
класс, который будет выполнять всю необходимую логику. Огромный плюс такого
подхода - в простоте использования и реализации; также данный код легко поддаётся
тестированию. Мы можем не изменять интерфейс IEmailCommunicator, а сделать обертку над существующими классами на
основании паттерна "фабричный метод". Но
поскольку мы предпочитаем нормальный подход для написания кода, изменим
данный класс; теперь
также можно избавиться от интерфейса ICommunicator. Посмотрим, что у нас получилось:
Интерфейс
IEmailCommunicator
public interface IEmailCommunicator
{
IMailCommunicator GetMailCommunicator(IEmailServerSettings settings);
}
Как видим, интерфейс IEmailCommunicator значительно
упростился. С реализацией тоже стало все очень просто и прозрачно.
public class EmailCommunicator : IEmailCommunicator
{
public IMailCommunicator GetMailCommunicator(IEmailServerSettings settings)
{
if (settings == null)
throw new ArgumentException("settings is null");
switch (settings.EmailServerType)
{
case EmailServerType.Imap:
return new ImapCommunicator(settings);
case EmailServerType.Pop3:
return new PopCommunicator(settings);
case EmailServerType.Smtp:
return new SmtpCommunicator(settings);
default:
throw new ArgumentException("Communicator not found");
}
}
}
Теперь класс EmailCommunicator, который составлял порядка 400 строк кода, стал очень
компактным. А вся логика по верификации перенеслась в конкретные классы. Для примера рассмотрим, как реализован класс SmtpCommunicator.
public class SmtpCommunicator : IMailCommunicator
{
#region [
vars ]
private readonly IEmailServerSettings _serverSettings;
#endregion
#region [
.ctor ]
public SmtpCommunicator(IEmailServerSettings serverSettings)
{
if (serverSettings == null)
throw new ArgumentNullException("serverSettings");
if (serverSettings.EmailServerType
!= EmailServerType.Smtp)
throw new ArgumentException("serverSettings has different
type from EmailServerType.Smtp");
_serverSettings = serverSettings;
}
#endregion
#region [
public methods ]
public string[] GetMessageUids()
{
throw new NotImplementedException("Smtp can't get message
uids");
}
public void Send(string @from, string to, string[] relatedFiles)
{
using (var mailer = new Smtp())
{
try
{
ServerAuthenticationInternal(mailer);
//
// Указываем параметры сообщения
//
mailer.From.AsString = String.IsNullOrEmpty(from)
? "default"
: from;
mailer.To.AsString = to;
mailer.Subject
= string.Empty;//заголовки не реализованы
mailer.BodyPlainText = string.Empty;//тело письма не реализовано
foreach (var file in relatedFiles)
{
mailer.AddAttachment(file);
}
//
// Отправляем
//
mailer.Send();
}
finally
{
try
{
if (mailer.IsConnected)
mailer.Disconnect();
}
// ReSharper disable
EmptyGeneralCatchClause
catch
// ReSharper restore
EmptyGeneralCatchClause
{
// Обработка исключения пропущена поскольку не корректная работа метода Disconnect()
// сделана на всякий случай не влияет на логику программы
}
}
}
}
public List<string> RecieveEmails(string[] notProcessedUids, string directoryToSave,
bool
isNeedToDeleteMessageAfterReceive)
{
throw new NotImplementedException("Smtp can't recieve
email");
}
public bool ServerAuthentication()
{
using (var mailer = new Smtp())
{
try
{
ServerAuthenticationInternal(mailer);
}
catch (Exception e)
{
//Log Exception
return false;
}
finally
{
try
{
if (mailer.IsConnected)
mailer.Disconnect();
}
// ReSharper disable
EmptyGeneralCatchClause
catch
// ReSharper restore
EmptyGeneralCatchClause
{
// Обработка исключения пропущена поскольку не корректная работа
метода Disconnect()
// сделана на всякий случай не влияет на логику программы
}
}
}
return true;
}
#endregion
#region [
private methods ]
private void ServerAuthenticationInternal(Smtp mailer)
{
if (mailer == null)
throw new ArgumentNullException("mailer");
int portInteger =
_serverSettings.Port;
// Создаем объект для указания параметров SMTP-сервера
//
var server = new SmtpServer
{
Name = _serverSettings.Address,
AuthMethods = string.IsNullOrEmpty(_serverSettings.UserName)
? AuthenticationMethods.None
: AuthenticationMethods.Auto,
AuthOptions = AuthenticationOptions.PreferSimpleMethods,
AccountName =
_serverSettings.UserName,
Port = portInteger,
Password =
_serverSettings.UserPassword
};
//
// Использование SSL
//
if (_serverSettings.UseSsl)
server.SslMode = SslStartupMode.OnConnect;
//
// Добавляем
параметры SMTP-сервера
//
mailer.DnsServers.Clear();
mailer.SmtpServers.Clear();
mailer.SmtpServers.Add(server);
//
// Аутентификация на SMTP-сервере
//
if (!mailer.IsConnected)
mailer.Connect();
mailer.Hello();
if (mailer.IsLoggedIn)
return;
var loginSuccess = mailer.Login();
if (!loginSuccess)
throw new Exception("Login error");
}
#endregion
}
Код стал
более компактным и позволяет расширять данный класс и добавлять необходимые проверки,
не боясь запутаться в собственном "спагетти коде". Также рассмотрим, как
реализован протокол Pop3.
public class PopCommunicator : IMailCommunicator
{
#region [
vars ]
private readonly IEmailServerSettings _serverSettings;
#endregion
#region [
.ctor ]
public PopCommunicator(IEmailServerSettings serverSettings)
{
if (serverSettings == null)
throw new ArgumentNullException("serverSettings");
if (serverSettings.EmailServerType
!= EmailServerType.Pop3)
throw new ArgumentException("serverSettings has different
type from EmailServerType.Pop3");
_serverSettings = serverSettings;
}
#endregion
#region [
public methods ]
public string[] GetMessageUids()
{
using (var pop3 = new Pop3())
{
pop3.InboxPreloadOptions = Pop3InboxPreloadOptions.Uidl;
ServerAuthAndLoginInternal(pop3);
string[] uids;
try
{
uids = pop3.GetMessageUids();
}
finally
{
try
{
if (pop3.IsConnected)
pop3.Disconnect();
}
// ReSharper disable
EmptyGeneralCatchClause
catch
// ReSharper restore
EmptyGeneralCatchClause
{
// Обработка исключения пропущена поскольку не корректная работа метода Disconnect()
// сделана на всякий случай не влияет на логику программы
}
}
return uids;
}
}
public void Send(string @from, string to, string[] relatedFiles)
{
throw new NotImplementedException("Pop protocol can't send
message");
}
public List<string> RecieveEmails(string[] notProcessedUids, string directoryToSave, bool
isNeedToDeleteMessageAfterReceive)
{
using (var pop3 = new Pop3())
{
try
{
pop3.InboxPreloadOptions = Pop3InboxPreloadOptions.Uidl;
//
//
Список имен файлов, которые были загруженны с ошибкой
//
var result = new List<string>();
//
//
Создаем временную папку для сохранения квитанций
//
try
{
if (!Directory.Exists(directoryToSave))
Directory.CreateDirectory(directoryToSave);
}
catch (Exception ex)
{
throw new Exception("Create save ticket
directory",
ex);
}
ServerAuthAndLoginInternal(pop3);
//
//
Получаем список уже прочитанных писем на РОР3-сервере для "отсеивания"
новых писем
//
foreach (var uid in notProcessedUids)
{
bool? isHandledTicketEmailMessage = null;
var messageIndex =
pop3.GetMessageIndexFromUid(uid);
var messageHeader =
pop3.DownloadMessageHeader(messageIndex);
if (IsHeaderCorrect(messageHeader))
{
//
// Загружаем сообщение
//
var mailMessage =
pop3.DownloadEntireMessage(messageIndex);
//
// Просматриваем и сохраняем приложения письма
//
foreach (var attachment in
mailMessage.Attachments.OfType<Attachment>())
{
//
// Записываем файл вложения в directoryToSave
//
try
{
var uniqueFileName = Path.Combine(directoryToSave,
attachment.FilenameOriginal);
File.WriteAllBytes(uniqueFileName,
attachment.GetData());
//
// Если при записи предыдущих вложений не возникало ошибок,
то помечаем письмо к удалению
//
if (isHandledTicketEmailMessage == null)
isHandledTicketEmailMessage = true;
}
catch
{
//Если при
получении возникла ошибка, то записываем имя файла в результат - как ошибочный
result.Add(attachment.Filename);
isHandledTicketEmailMessage = false;
}
}
}
//
// Удаляем письмо с POP3-сервера, если указан признак необходимости
// удаления письма после получения и в письме содержится подходяшее
приложение
//
try
{
if
(isNeedToDeleteMessageAfterReceive
&&
isHandledTicketEmailMessage.HasValue
&&
isHandledTicketEmailMessage.Value)
{
pop3.DeleteMessage(messageIndex);
}
}
// ReSharper disable
EmptyGeneralCatchClause
catch
// ReSharper restore
EmptyGeneralCatchClause
{
// Ошибку удаления письма пользователю не показываем
}
}
return result;
}
finally
{
try
{
if (pop3.IsConnected)
pop3.Disconnect();
}
// ReSharper disable
EmptyGeneralCatchClause
catch
// ReSharper restore
EmptyGeneralCatchClause
{
// Обработка исключения пропущена поскольку не корректная работа метода Disconnect()
// сделана на всякий случай не влияет на логику программы
}
}
}
}
public bool ServerAuthentication()
{
using (var pop3 = new Pop3())
{
try
{
ServerAuthAndLoginInternal(pop3);
}
catch (Exception)
{
return false;
}
finally
{
try
{
if (pop3.IsConnected)
pop3.Disconnect();
}
// ReSharper disable
EmptyGeneralCatchClause
catch
// ReSharper restore
EmptyGeneralCatchClause
{
// Обработка исключения пропущена поскольку не корректная работа метода Disconnect()
// сделана на всякий случай не влияет на логику программы
}
}
}
return true;
}
#endregion
#region [
private methods ]
private void ServerAuthAndLoginInternal(Pop3 pop3)
{
if (pop3 == null)
throw new ArgumentNullException("pop3");
int portInteger =
_serverSettings.Port;
if (_serverSettings.UseSsl)
pop3.SslMode = SslStartupMode.OnConnect;
//
// Соединяемся с POP3-сервером и проводим аутентификацию
//
if (!pop3.IsConnected)
pop3.Connect(_serverSettings.Address, portInteger);
if (!pop3.IsLoggedIn)
pop3.Login(_serverSettings.UserName,
_serverSettings.UserPassword,
AuthenticationMethods.Auto,
AuthenticationOptions.PreferSimpleMethods,
null);
}
private bool IsHeaderCorrect(MailMessage header)
{
return header != null &&
!string.IsNullOrEmpty((string)header.UidOnServer);
}
#endregion
}
Добавление
паттерна "фабричный метод" значительно
упростило нам реализацию данного кода и позволило без проблем добавить поддержку
нового протокола. Если Вам вдруг понадобится добавить какой-либо протокол ещё,
Вы можете сделать это без проблем. Также огромным плюсом данного подхода
является то, что Вы можете покрыть данные классы интеграционными и юнит-тестами, что нельзя было сделать в первом коде. А благодаря использованию IoC контейнеров, как, например, Autofac Вы можете сделать использование данного подхода гибким и элегантным. Надеюсь, что статья подвигнет Вас к
изучению паттернов проектирования, если Вы с ними еще не знакомы. Исходники к данной статье можно скачать по
ссылке TestEmailWorker.