如何使用IHttpClientFactory替代WebClient向FTP Server上传文件?
替代已弃用WebClient实现FTP文件上传(适配依赖注入方案)
问题背景
原有代码使用已过时的WebClient实现FTP文件上传,收到官方过时警告:
SYSLIB0014 'WebClient.WebClient()' is obsolete: 'WebRequest, HttpWebRequest, ServicePoint, and WebClient are obsolete. Use HttpClient instead.'
原有实现代码:
public async Task<BaseResponse<Void>> Handle(FTPUploadRequest request, CancellationToken cancellationToken) { using WebClient client = new() { Credentials = new NetworkCredential(configuration.FTP.User, configuration.FTP.Password.Decrypt()) }; static string BuildUri(FTPUploadRequest request, IOptionsSnapshot<Configuration> configuration) => $"ftp://{configuration.FTP.Server}/{request.DirectoryPath}/{request.FileName}"; client.UploadFile( BuildUri(request, configuration), WebRequestMethods.Ftp.UploadFile, request.FilePath + "/" + request.FileName); return await BaseResponse<Void>.Create(true); }
需求是通过IHttpClientFactory替代现有实现,但不清楚FTP上传的正确方式,疑惑是否需要使用multipart请求。
关键说明
HttpClient不支持FTP协议,它仅为HTTP/HTTPS通信设计,因此无法直接用IHttpClientFactory实现FTP上传,也不需要使用multipart-message(这是HTTP文件上传的规范,和FTP协议无关)。
以下是两种可行的替代方案:
方案一:基于FtpWebRequest重构(原生兼容,支持依赖注入)
虽然FtpWebRequest也标记为过时,但目前仍能正常运行,可通过封装接口的方式提升可测试性,同时适配依赖注入。
- 定义FTP客户端接口:
public interface IFtpClient { Task UploadFileAsync(string server, string username, string password, string remotePath, string localFilePath, CancellationToken cancellationToken); }
- 实现接口(基于FtpWebRequest):
public class FtpWebRequestClient : IFtpClient { public async Task UploadFileAsync(string server, string username, string password, string remotePath, string localFilePath, CancellationToken cancellationToken) { var ftpUri = new Uri($"ftp://{server}/{remotePath}"); using var request = (FtpWebRequest)WebRequest.Create(ftpUri); request.Method = WebRequestMethods.Ftp.UploadFile; request.Credentials = new NetworkCredential(username, password); using var fileStream = new FileStream(localFilePath, FileMode.Open, FileAccess.Read); using var requestStream = await request.GetRequestStreamAsync(cancellationToken); await fileStream.CopyToAsync(requestStream, cancellationToken); using var response = (FtpWebResponse)await request.GetResponseAsync(cancellationToken); // 可根据需求检查响应状态,比如response.StatusCode } }
- 注册依赖到容器:
services.AddScoped<IFtpClient, FtpWebRequestClient>();
- 重构原有Handle方法:
private readonly IFtpClient _ftpClient; private readonly IOptionsSnapshot<Configuration> _configuration; // 构造函数注入依赖 public YourHandler(IFtpClient ftpClient, IOptionsSnapshot<Configuration> configuration) { _ftpClient = ftpClient; _configuration = configuration; } public async Task<BaseResponse<Void>> Handle(FTPUploadRequest request, CancellationToken cancellationToken) { var ftpConfig = _configuration.FTP; var remotePath = $"{request.DirectoryPath}/{request.FileName}"; var localFilePath = Path.Combine(request.FilePath, request.FileName); await _ftpClient.UploadFileAsync( ftpConfig.Server, ftpConfig.User, ftpConfig.Password.Decrypt(), remotePath, localFilePath, cancellationToken); return await BaseResponse<Void>.Create(true); }
方案二:使用第三方FTP库(推荐,无过时问题,易测试)
推荐使用FluentFTP成熟第三方库,它支持.NET Core/.NET 5+,原生支持异步操作和依赖注入,且API设计更友好,便于单元测试。
- 安装NuGet包:
Install-Package FluentFTP
- 注册FluentFTP到依赖注入容器:
services.AddFluentFTP(c => { c.ConfigureClient = (client, serviceProvider) => { var config = serviceProvider.GetRequiredService<IOptionsSnapshot<Configuration>>().FTP; client.Host = config.Server; client.Credentials = new NetworkCredential(config.User, config.Password.Decrypt()); // 可按需配置端口、加密方式等FTP参数 }; });
- 重构Handle方法:
private readonly IFtpClient _ftpClient; private readonly IOptionsSnapshot<Configuration> _configuration; public YourHandler(IFtpClient ftpClient, IOptionsSnapshot<Configuration> configuration) { _ftpClient = ftpClient; _configuration = configuration; } public async Task<BaseResponse<Void>> Handle(FTPUploadRequest request, CancellationToken cancellationToken) { var localFilePath = Path.Combine(request.FilePath, request.FileName); var remotePath = $"/{request.DirectoryPath}/{request.FileName}"; // FluentFTP建议使用绝对路径 await _ftpClient.ConnectAsync(cancellationToken); try { var uploadStatus = await _ftpClient.UploadFileAsync(localFilePath, remotePath, FtpRemoteExists.Overwrite, true, cancellationToken); // 可根据uploadStatus判断上传结果 } finally { await _ftpClient.DisconnectAsync(cancellationToken); } return await BaseResponse<Void>.Create(true); }
该方案不仅规避了过时API问题,还提供了更丰富的FTP操作能力(如文件夹创建、文件列表查询等),单元测试时可通过Moq等框架模拟IFtpClient行为,提升测试效率。
内容的提问来源于stack exchange,提问作者decius
相关产品推荐
相关产品推荐

