
基于C#.NET编写的FTP客户端界面是WPF框架支持遍历FTP服务器目录文件下载上传删除等功能支持二次开发写WPFC# FTP客户端这事儿完全是被Windows自带资源管理器的FTP逼出来的——点半天刷新不出列表传个大文件卡了之后全重传连个批量删除的确认框都做得像Windows 98忍无可忍。折腾了一周搞出个基础但够用的原生System.Net.FtpWebRequest写后端MVVM模式的WPF写界面支持主流的遍历、下载/上传带原生断点续传、删除二次开发门槛极低——因为基本没加第三方重依赖都是微软自带的东西想加功能或者换皮肤直接改就行。先讲最容易踩坑的目录遍历原生提供了两个方法ListDirectory和ListDirectoryDetails。一开始我天真用了前者返回的全是一行一行的纯路径比如/test/1.txt根本分不清是文件还是文件夹还要再单独发个WebRequestMethods.Ftp.GetFileSize请求试错——如果报错550路径不存在或者是目录就当文件夹处理太蠢了服务器延迟一高整个界面卡得像PPT。果断换ListDirectoryDetails虽然不同FTP服务器Windows IIS/FileZilla/VSCode的Live Server的返回格式细节比如时间、权限的列位有点反人类但主流的开头第一个字符都是判断依据d开头就是目录-开头就是文件。基础版先抓这个二次开发的话可以整个小规则类比如存Dictionary DirectoryFlagIndex适配更多奇怪的服务器。// 后端Model层的核心遍历方法加了可选的深度限制防止太深递归栈溢出 public async TaskListFtpItem ListDirectoryAsync(string path, int maxDepth 5, int currentDepth 0) { var items new ListFtpItem(); if (currentDepth maxDepth) { // 二次开发可以把这里改成弹用户提示或者配置项改成可输入 Debug.WriteLine($当前目录层级已达默认限制{maxDepth}请手动调整); return items; } var request (FtpWebRequest)WebRequest.Create($ftp://{_serverAddress}:{_port}/{path.Trim(/)}); request.Credentials new NetworkCredential(_username, _password); request.Method WebRequestMethods.Ftp.ListDirectoryDetails; request.UsePassive _usePassive; // 被动/主动模式二次开发可以加个界面开关 try { using var response (FtpWebResponse)await request.GetResponseAsync(); using var stream response.GetResponseStream(); using var reader new StreamReader(stream, Encoding.UTF8); string line; while ((line await reader.ReadLineAsync()) ! null) { // 主流FileZilla/IIS格式列是按空格分的跳过空行 if (string.IsNullOrWhiteSpace(line)) continue; var parts line.Split(new[] { }, StringSplitOptions.RemoveEmptyEntries); if (parts.Length 9) continue; // 不够列的直接忽略大概率是服务器的奇怪提示 bool isDirectory parts[0].StartsWith(d); string name string.Join( , parts.Skip(8)); // 文件名可能有空格要拼后面所有 string fullPath ${path.Trim(/)}/{name}; items.Add(new FtpItem { Name name, FullPath fullPath, IsDirectory isDirectory, Size isDirectory ? 0 : long.TryParse(parts[4], out long size) ? size : 0, // 时间解析二次开发可以适配不同格式这里先抓FileZilla的MMM dd HH:mm或者yyyy ModifiedTime DateTime.TryParse(${parts[5]} {parts[6]} {parts[7]}, out var dt) ? dt : DateTime.MinValue }); } } catch (WebException ex) { // 错误处理二次开发可以扩展比如530权限不足550路径不存在 Debug.WriteLine($遍历目录失败{ex.Message}); throw; } return items; }接下来是带断点续传的下载原生方法真的太香了不用引入FluentFTP这种大库虽然大库功能全但二次开发怕依赖版本冲突核心就是两个ContentOffset属性和本地FileStream的Seek。基于C#.NET编写的FTP客户端界面是WPF框架支持遍历FTP服务器目录文件下载上传删除等功能支持二次开发一开始我忘写Seek了下载了个2G的奥特曼剧场版一半断了我重连再点下载以为可以续上结果打开发现前半段昭和画质后半段黑屏——哦对哦FtpWebRequest只是从服务器的偏移量开始读但本地的FileStream还是默认从0开始写啊傻透了加一行就解决。// 下载方法enableResume是可选参数默认false二次开发可以加个界面勾选框 public async Task DownloadFileAsync(string remotePath, string localPath, bool enableResume false, IProgresslong progress null) { long localFileLength 0; if (enableResume File.Exists(localPath)) { localFileLength new FileInfo(localPath).Length; } var request (FtpWebRequest)WebRequest.Create($ftp://{_serverAddress}:{_port}/{remotePath.Trim(/)}); request.Credentials new NetworkCredential(_username, _password); request.Method WebRequestMethods.Ftp.DownloadFile; request.UsePassive _usePassive; request.ContentOffset localFileLength; // 核心从服务器的这个位置开始读 try { using var response (FtpWebResponse)await request.GetResponseAsync(); using var remoteStream response.GetResponseStream(); // 核心本地文件如果存在且续传就用Append模式不对Append模式会自动Seek末尾但单独Seek更直观也方便新手二次开发理解 using var localStream new FileStream(localPath, FileMode.OpenOrCreate, FileAccess.Write); localStream.Seek(localFileLength, SeekOrigin.Begin); var buffer new byte[8192]; // 8KB缓冲二次开发可以改成可配置 int bytesRead; long totalRead localFileLength; while ((bytesRead await remoteStream.ReadAsync(buffer, 0, buffer.Length)) 0) { await localStream.WriteAsync(buffer, 0, bytesRead); totalRead bytesRead; progress?.Report(totalRead); // 进度条IProgress是线程安全的完美适配WPF } } catch (WebException ex) { // 550可能是远程文件不存在或者ContentOffset超过了远程文件大小二次开发可以判断后者自动全传 Debug.WriteLine($下载失败{ex.Message}); throw; } }上传和删除逻辑其实和这两个差不多上传也是用ContentOffset本地FileStream要Seek到对应位置读上传前记得先GetFileSize看看远程有没有同名文件删除分文件和目录目录要递归删——原生DeleteDirectory只能删空的先把里面的子文件全DeleteFile子目录全递归Delete最后删父目录。这些代码太长了就不全贴了核心逻辑都一样微软官方文档也有参考不过我自己加了个批量删除失败的ObservableCollection二次开发可以直接用这个做失败重删列表。然后是WPF界面用MVVM模式的原因就是二次开发太爽了——界面和逻辑完全分开。比如想把默认的白色改成暗色主题直接在XAML的ResourceDictionary里换Brush就行不用动ViewModel的一行代码想加个批量下载的功能直接在MainViewModel里加个ObservableCollection SelectedItems再写个超轻量的RelayCommand不用第三方库20多行搞定// 界面ViewModel层的超轻量RelayCommand随便拿随便改 public class RelayCommand : ICommand { private readonly Actionobject _execute; private readonly Funcobject, bool _canExecute; public RelayCommand(Actionobject execute, Funcobject, bool canExecute null) { _execute execute ?? throw new ArgumentNullException(nameof(execute)); _canExecute canExecute; } public bool CanExecute(object parameter) _canExecute?.Invoke(parameter) ?? true; public void Execute(object parameter) _execute(parameter); public event EventHandler CanExecuteChanged { add CommandManager.RequerySuggested value; remove CommandManager.RequerySuggested - value; } }然后XAML的TreeView和DataGrid绑定SelectedItems下载按钮的Command绑定批量下载的RelayCommandCanExecute绑定SelectedItems.Count 0完美按钮会自动变灰或者高亮。整个项目的结构也很简单就三层Model放FtpWebRequest的核心逻辑、FtpItem实体、ViewModel放界面的命令、属性、ObservableCollection、View放XAML界面。二次开发的话比如想加个FTP服务器的历史记录功能直接在Model加个Json序列化的类存ServerHistoryViewModel加个ObservableCollectionView加个ComboBox绑定就行想加个FTP的搜索功能直接在Model加个递归搜索Name的方法ViewModel加个SearchText属性和SearchCommand完美。代码我已经传到GitHub上了不过这里就不说链接了怕被当成广告感兴趣的朋友可以按照上面的逻辑自己写或者私信我要。总的来说原生C#.NET写FTP客户端完全够用不用迷信大库二次开发也非常自由WPF的MVVM模式虽然一开始学起来有点绕但学会之后改界面真的太爽了。