Accueil Nos publications Blog C# – Un FtpHelper allégé

C# – Un FtpHelper allégé

J’ai réalisé il y a quelques temps une petite classe simple permettant d’effectuer des manipulations de base sur un serveur FTP (Suppression, Upload, Download, Listing, etc. …) dans un environnement WPF / Silverlight (non compatible dans un environnement Windows Phone 7 pour le moment, il faudrait utiliser des requêtes HTTP).

Quelles classes choisir ?

C’est vrai qu’il existe plusieurs méthodes différentes pour parvenir à réaliser ce genre de classe (on peut penser à la classe WebClient qui propose des méthodes de transferts asynchrones par exemple), mais j’ai privilégié l’utilisation des classes Ftp* (FtpWebRequest, FtpWebResponse) et ce, pour plusieurs raisons :

  • Utiliser des objets communs pour l’ensemble des opérations qui seront réalisées sur le FTP. La classe WebClient ne permet que d’envoyer et recevoir des données : il n’est pas possible de lister un répertoire distant, supprimer des ressources distantes, etc. … Si j’utilisais cette classe, j’aurais quand même dû utiliser FtpWebRequest et FtpWebResponse pour lister, supprimer, etc. …
  • Liberté de gestion des transferts asynchrones (par exemple pour gérer plusieurs actions lors des transferts ou encore désactiver des contrôles, etc. …)
  • Gestion de l’état des transferts

Si votre besoin est lié uniquement à un simple transfert sans attente quelconque en retour, vous pouvez bien entendu utiliser la classe WebClient.

Classe FtpHelper

La classe FtpHelper est simple et allégée. Voici son code :


public class FtpHelper : INotifyPropertyChanged
{
#region Private Fields
const int bufferLength = 2048;
private readonly string[] _separators = new string[] { "\r\n" };
private String _Url;
private String _Login;
private String _Password;
private Boolean _IsRequestCompleted;
private int _currentPercentage;
#endregion

#region Ctors

public FtpHelper() { }

public FtpHelper(String url, String login, String password)
{
_Url = url;
_Login = login;
_Password = password;
}

#endregion

#region Public Methods

///
/// Get File Size
///
///
///
public long GetFileSize(String file)
{
try
{
var ftpFullFilePath = GetFileFtpUrl(file);
var request = Request(ftpFullFilePath);
request.Method = WebRequestMethods.Ftp.GetFileSize;

long size;
using (var response = (FtpWebResponse)request.GetResponse()) {
size = response.ContentLength;
}
return size;
}
catch { throw new Exception("Could not Get File Size"); }
}

///
/// Check if a file exist
///
///
///
public bool FileExists(String file)
{
try {
var ftpFullFilePath = GetFileFtpUrl(file);
var request = Request(ftpFullFilePath);
request.Method = WebRequestMethods.Ftp.GetFileSize;
var response = (FtpWebResponse)request.GetResponse();
return true;
}
catch (WebException ex)
{
var response = (FtpWebResponse)ex.Response;
if (response.StatusCode == FtpStatusCode.ActionNotTakenFileUnavailable) {
return false;
}
throw new Exception("Can not determine if file exists or not");
}
}

///
/// Deletes file on ftp
///
///
///
public bool Delete(String file)
{
try
{
var ftpFullFilePath = GetFileFtpUrl(file);
var request = Request(ftpFullFilePath);
request.Method = WebRequestMethods.Ftp.DeleteFile;
var response = (FtpWebResponse)request.GetResponse();
return true;
}
catch (WebException ex)
{
var response = (FtpWebResponse)ex.Response;
if (response.StatusCode == FtpStatusCode.ActionNotTakenFileUnavailable) {
return false;
}
throw new Exception("Can not delete file exists or not");
}
}

///
/// Upload file
///
///
public void Upload(String file)
{
IsRequestCompleted = false;
var ftpFile = GetFileFtpUrl(file);

try
{
if (FileExists(file)) {
Delete(file);
}

var request = Request(ftpFile);
request.Method = WebRequestMethods.Ftp.UploadFile;
request.ContentLength = new FileInfo(file).Length;
Stream requestStream = request.GetRequestStream();

//const int bufferLength = 2048;
byte[] buffer = new byte[bufferLength];
int count = 0;
int readBytes = 0;
CurrentPercentage = 0;

using (FileStream stream = File.OpenRead(file))
{
do
{
readBytes = stream.Read(buffer, 0, bufferLength);
requestStream.Write(buffer, 0, readBytes);
count += readBytes;

//You can upload a 0 bytes file but, we must not process percentage
//which is base on length
if (request.ContentLength > 0)
{
var currentP = (decimal)((count / request.ContentLength) * 100);
if (CurrentPercentage < 100)
{
CurrentPercentage = (int)Math.Round(currentP, MidpointRounding.ToEven);
}
}
}
while (readBytes != 0);
}
requestStream.Close();
CurrentPercentage = 100;
}
catch(Exception ex) {
throw new Exception("Could not upload file properly.", ex);
}

IsRequestCompleted = true;
}

///
/// Download File
///
///
public void Download(String file)
{
Download(file, null);
}

///
/// Download file
///
///
///
public void Download(String file, String outputPath)
{
//throw new NotImplementedException("Download has not already been implemented");
IsRequestCompleted = false;
var ftpFile = GetFileFtpUrl(file);

if (String.IsNullOrEmpty(outputPath)) {
outputPath = Environment.CurrentDirectory;
}

var outputFile = Path.Combine(outputPath, Path.GetFileName(file));

try {

var request = Request(ftpFile);
request.Method = WebRequestMethods.Ftp.DownloadFile;
request.ContentLength = GetFileSize(file);
Stream responseStream = request.GetResponse().GetResponseStream();

byte[] buffer = new byte[bufferLength];
int count = 0;
int readBytes = 0;
CurrentPercentage = 0;

using (FileStream stream = File.Create(outputFile))
{
do
{
readBytes = responseStream.Read(buffer, 0, buffer.Length);
stream.Write(buffer, 0, readBytes);
count += readBytes;

//You can download a 0 bytes file but, we must not process percentage
//which is base on length
if (request.ContentLength > 0)
{
var currentP = (decimal)((count / request.ContentLength) * 100);
if (CurrentPercentage < 100) {
CurrentPercentage = (int)Math.Round(currentP, MidpointRounding.ToEven);
}
}
}
while (readBytes != 0);
}
responseStream.Close();
CurrentPercentage = 100;
}
catch (Exception ex) {
throw new Exception("Could not download file properly.", ex);
}

IsRequestCompleted = true;

}

///
/// List base directory (url) files
///
public String[] GetDirectoryFiles()
{
try
{
var request = Request(_Url);
request.Method = WebRequestMethods.Ftp.ListDirectory;
var response = request.GetResponse();
String expectedResult = String.Empty;
using (response)
{
using (StreamReader sr = new StreamReader(response.GetResponseStream()))
{
expectedResult = sr.ReadToEnd();
}
}
if (!String.IsNullOrEmpty(expectedResult))
{
String[] files = expectedResult.Split(_separators, StringSplitOptions.RemoveEmptyEntries);
return files;
}
}
catch(Exception ex) {
throw new Exception("Can not list directory", ex);
}
return null;
}

#endregion

#region Private Methods
///
/// Return a new FtpWebRequest with default parameters
///
/// Url to connect
/// return a new FtpWebRequest
private FtpWebRequest Request(String Url)
{
var request = (FtpWebRequest)WebRequest.Create(Url);
request.Credentials = new NetworkCredential(_Login, _Password);
request.KeepAlive = false;
request.UseBinary = true;
return request;
}

///
/// Get expected file url on ftp;
///
///
///
private String GetFileFtpUrl(String file)
{
if (file.StartsWith("ftp://"))
return file;

FileInfo fi = new FileInfo(file);
Uri baseUrl = new Uri(_Url);
Uri destUri;

if (Uri.TryCreate(baseUrl, fi.Name, out destUri)) {
return destUri.AbsoluteUri;
}
return String.Empty;
}

#endregion

#region Public Properties
public String Password
{
get { return _Password; }
set { _Password = value; }
}

public String Login
{
get { return _Login; }
set { _Login = value; }
}

public String Url
{
get { return _Url; }
set { _Url = value; }
}
///
/// gets / sets if current request is completed
///
public Boolean IsRequestCompleted
{
get { return _IsRequestCompleted; }
set
{
if (_IsRequestCompleted != value) {
_IsRequestCompleted = value;
RaisePropertyChanged("IsRequestCompleted");
}
}
}

///
/// gets / sets current file being downloaded or upload size
///
public int CurrentPercentage
{
get { return _currentPercentage; }
set
{
if (_currentPercentage != value)
{
_currentPercentage = value;
RaisePropertyChanged("CurrentPercentage");
}
}
}

#endregion

#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler propertyChanged = this.PropertyChanged;
if (propertyChanged != null)
{
propertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}

Utilisation du Helper

Pour pouvoir envoyer un fichier, il faudrait initialiser la classe avec les informations de connexion :


FtpHelper helper = new FtpHelper("ftp.example.com", "login", "password");

Pour pouvoir transférer un fichier vers le FTP :


helper.Upload("monfichier.ext");

Pour télécharger un fichier vers son poste :


helper.Download("nomdufichier")

Ou


Helper.Download("nomdufichier", @"D:\DestDirectory\")

Pour connaître l’état d’avancement du transfert, il est possible de se “binder” sur la propriété CurrentPercentage


helper.CurrentPercentage

Pour déterminer si une requête est complètement exécutée (transfert fini), il est possible de se binder sur la propriété IsRequestCompleted :


IsRequestCompleted.

Pour finir

Bien entendu, cette classe est modifiable à votre convenance (et peut être davantage améliorée). Vous pourrez trouver un exemple (WPF) d’utilisation plus précise de cette classe en cliquant ici.

A bientôt.