C# – Un FtpHelper allégé

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.
