You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用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也标记为过时,但目前仍能正常运行,可通过封装接口的方式提升可测试性,同时适配依赖注入。

  1. 定义FTP客户端接口:
public interface IFtpClient
{
    Task UploadFileAsync(string server, string username, string password, string remotePath, string localFilePath, CancellationToken cancellationToken);
}
  1. 实现接口(基于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
    }
}
  1. 注册依赖到容器:
services.AddScoped<IFtpClient, FtpWebRequestClient>();
  1. 重构原有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设计更友好,便于单元测试。

  1. 安装NuGet包:
Install-Package FluentFTP
  1. 注册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参数
    };
});
  1. 重构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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 01:18:12