[.NET, Проектирование и рефакторинг, C#] Организация кода для работы с ftp средствами Fluent interface

Автор Сообщение
news_bot ®

Стаж: 6 лет 9 месяцев
Сообщений: 27286

Создавать темы news_bot ® написал(а)
14-Июл-2021 13:31

Мне очень нравится паттерн Fluent interface, за то, что он делает сложный и длинный код максимально читабельным. В статье хочу показать пример реализации этого паттерна при работе с ftp. Задача, что требуется сделать:
  • Получать имена файлов в определенном каталоге;
  • Скачивать файлы в поток/файл;
  • Загружать файлы из потока/файла;
  • Удалять файлы;
  • Настройки данных авторизации(ip, port, login, name).
Необходимо получить код, который будет лаконичным, читабельным и при помощи IntelliSense обеспечить легкое и удобное потребление кода. Пример:
_ftpService
  .OnConfigurate(pathSource)
  .Download(file)
  .ToFile(localFile);
и/или
_ftpService
  .OnConfigurate(pathSource)
  .Download(file)
  .ToSteam(memStream);
и/или
_ftpService
    .OnConfigurate(pathDestination)
    .Upload(fileNameDestination)
    .FromStream(memStream);
Для начала определяем интерфейсы по принципам SRP(единственной ответственности):
/// <summary>
/// Интерфейс настройки фтп сервиса
/// </summary>
public interface ITransferFileService
{
  string Url { get; }
  ITransferServiceAction OnConfigurate(string path);
}
/// <summary>
/// Интерфейс записи данных с фтп
/// </summary>
public interface ITransferServiceWrite
{
  void FromFile(string filePath);
  void FromStream(Stream stream);
}
/// <summary>
/// Интерфейс чтения данных с фтп
/// </summary>
public interface ITransferServiceRead
{
  void ToFile(string filePath);
  void ToStream(Stream stream);
}
/// <summaty>
/// Интерфейс доступных действий с фтп
/// </summary>
public interface ITransferServiceAction
{
  ITransferServiceRead Download(string fileName);
  ITransferServiceWrite Upload(string fileName);
  void Delete(string fileName);
  IEnumerable<string> GetNameFiles();
}
Теперь добавим класс с реализацией описанных выше интерфейсов.
public class FtpService :
    ITransferFileService,
    ITransferServiceAction,
    ITransferServiceRead,
    ITransferServiceWrite
  {
    private readonly Logger _logger;
    private string _fileName;
    public FtpService(string url, ILogger logger)
    {
      _logger = logger;
      Url = url;
    }
    public string Url { get; }
    /// <summary>
    /// Порт(по умолчанию 21)
    /// </summary>
    public int Port { get; private set; } = 21;
    /// <summary>
    /// Пароль для подключения к фтп
    /// </summary>
    private string Password { get; set; }
    /// <summary>
    /// Логин для подключения в фтп
    /// </summary>
    private string Login { get; set;}
    /// <summary>
    /// Путь
    /// </summary>
    private string Path { get; set; }
    public void SetCredential(string login, string password)
    {
      Login = login;
      Password = password;
    }
    public ITransferServiceAction OnConfigurate(string path)
    {
      Path = path;
      return this;
    }
    public ITransferServiceRead Download(string fileName)
    {
      _fileName = fileName;
      return this;
    }
    public ITransferServiceWrite Upload(string fileName)
    {
      _fileName = fileName;
      return this;
    }
    public void Delete(string fileName)
    {
      try
      {
        var request = (FtpWebRequest) WebRequest.Create($"{Url}/{Path}/{_fileName}");
        request.Credentials = new NetworkCredential(Login,Password);
        request.Method = WebRequestMethods.Ftp.DeleteFile;
        request.GetResponse();
      }
      catch (Exception ex)
      {
        _logger.Error($"Ошибка удаления файла с ftp сервера - {ex.Message} ");
      }
    }
    public void FromFile(string filePath)
    {
      if(string.IsNullOrEmpty(filePath))
        return;
      try
      {
        using (var client = new WebClient())
        {
          client.Credentials = new NetworkCredential(Login, Password);
          client.UploadFile($"{Url}/{Path}/{_fileName}", WebRequestMethods.Ftp.UploadFile, filePath);
        }
      }
      catch (Exception ex)
      {
        _logger.Error(ex);
      }
    }
    public void FromStream(Stream stream)
    {
      if(stream == null)
        return;
      try
      {
        var request =
          (FtpWebRequest)WebRequest.Create($"{Url}/{Path}/{_fileName}");
        request.Credentials = new NetworkCredential(Login, Password);
        request.UsePassive = true;
        request.UsePassive = true;
        request.KeepAlive = true;
        request.Method = WebRequestMethods.Ftp.UploadFile;
        using (var ftpStream = request.GetRequestStream())
        {
          stream.CopyTo(ftpStream);
        }
        request.GetResponse();
      }
      catch (Exception ex)
      {
        _logger.Error(ex);
      }
    }
    private byte[] DownloadFile()
    {
      var ftpRequest = (FtpWebRequest)WebRequest.Create($"{Url}/{Path}/{_fileName}");
      ftpRequest.Credentials = new NetworkCredential(Login, Password);
      ftpRequest.UseBinary = true;
      ftpRequest.UsePassive = true;
      ftpRequest.KeepAlive = true;
      ftpRequest.Method = WebRequestMethods.Ftp.DownloadFile;
      var ftpResponse = (FtpWebResponse)ftpRequest.GetResponse();
      using (var ms = new MemoryStream())
      {
        ftpResponse.GetResponseStream().CopyTo(ms);
        return ms.ToArray();
      }
    }
    public void ToFile(string filePath)
    {
      try
      {
        var downloadedFile = DownloadFile();
        File.WriteAllBytes(filePath, downloadedFile);
      }
      catch (WebException ex)
      {
        _logger.Error(Url);
        _logger.Error(ex);
      }
    }
    public void ToStream(Stream stream)
    {
      if (stream == null)
        return;
      try
      {
        using (var writer = new BinaryWriter(stream))
        {
          var downloadedFile = DownloadFile();
          writer.Write(downloadedFile);
        }
      }
      catch (WebException ex)
      {
        _logger.Error(Url);
        _logger.Error(ex);
      }
    }
    public IEnumerable<string> GetNameFiles()
    {
      var request = (FtpWebRequest)WebRequest.Create($"{Url}/{Path}");
      request.Method = WebRequestMethods.Ftp.ListDirectory;
      request.Credentials = new NetworkCredential(Login, Password);
      var files = new List<string>();
      using (var response = (FtpWebResponse)request.GetResponse())
      {
        var responseStream = response.GetResponseStream();
        using (var reader = new StreamReader(responseStream))
        {
          var line = reader.ReadLine();
          while (!string.IsNullOrEmpty(line))
          {
            try
            {
              files.Add(line);
              line = reader.ReadLine();
            }
            catch (Exception ex)
            {
              _logger.Error(ex);
            }
          }
        }
      }
      return files;
    }
    public void SetPort(int port)
    {
      Port = port;
    }
  }
Что можно сделать лучше - добавить асинхронный вариант цепочки. Ничего сложно в этом нет, достаточно добавить в интерфейсы методы с возвращаемым типом Task<T>. Для гибкой настройки сервиса добавим паттерн строитель:
/// <summary>
/// Построитель Ftp сервиса
/// </summary>
public class BuilderFtpService
{
  private FtpService ftpService { get; }
  /// <summary>
  /// Конструктор с ip адресом
  /// </summary>
  /// <param name="url">Адрес фтп</param>
  /// <param name="logger">Логгер</param>
  public BuilderFtpService(string url, ILogger logger)
  {
    ftpService = new FtpService(url,logger);
  }
  /// <summary>
  /// Построить экземпляр сервиса
  /// </summary>
  public ITransferFileService Build() => ftpService;
  /// <summary>
  /// Указать авторизационные данные
  /// </summary>
  /// <param name="login">Логин</param>
  /// <param name="password">Пароль</param>
  /// <returns>Построитель фтп сервиса</returns>
  public BuilderFtpService WithCredential(string login, string password)
  {
    ftpService.SetCredential(login, password);
    return this;
  }
  /// <summary>
  /// Указать авторизационные данные
  /// </summary>
  /// <param name="login">Логин</param>
  /// <param name="password">Пароль</param>
  /// <returns>Построитель фтп сервиса</returns>
  public BuilderFtpService WithPort(int port)
  {
    ftpService.SetPort(port);
    return this;
  }
}
Таким образом у нас получилось реализовать функционал согласно поставленной задачи. Пример настройки и использования:
new BuilderFtpService(ipAddress, logger)
  .WithCredential(login, password)
  .Build()
  .OnConfigurate(pathSource)
  .Download(file)
  .ToFile(localFile);
Код выше приведен в качестве примера, в реальном приложении рекомендуется все зависимости реализовывать через IoC контейнеры. Такая реализация функционала имеет лаконичный вид, высокую читабельность и повышает интуитивность использования кода. Как и в любом подходе, паттерн fluent interface имеет минусы - проблема отладки. В длинных цепочках вызовов трудно поставить точку остановки.
===========
Источник:
habr.com
===========

Похожие новости: Теги для поиска: #_.net, #_proektirovanie_i_refaktoring (Проектирование и рефакторинг), #_c#, #_c#, #_.net, #_patterns, #_fluentinterface, #_.net, #_proektirovanie_i_refaktoring (
Проектирование и рефакторинг
)
, #_c#
Профиль  ЛС 
Показать сообщения:     

Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы

Текущее время: 22-Ноя 13:49
Часовой пояс: UTC + 5