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

.NET 6中向IServiceCollection注册FluentFTP的最佳实践

在.NET 6中注册FluentFTP的FtpClient到IServiceCollection的最佳实践

类似IHttpClientFactory的实现思路

FluentFTP官方没有提供和IHttpClientFactory完全对应的原生扩展,但我们可以参考AddHttpClient的设计模式,实现一套可复用、支持配置管理和生命周期控制的注册方案,解决你现有代码灵活性不足的问题。


步骤1:优化配置类

先确保配置类能完整承载FTP连接参数:

public class MyFtpClientSettings
{
    public string Host { get; set; } = string.Empty;
    public int Port { get; set; } = 21;
    public string Username { get; set; } = string.Empty;
    public string Password { get; set; } = string.Empty;
    public bool EnableSsl { get; set; } = false;
    // 可按需添加超时、被动模式等其他配置项
}

步骤2:重构FTP客户端包装类

让包装类依赖配置注入,同时封装FluentFTP的核心操作(或按需暴露原生客户端):

public class MyFtpClient : IDisposable
{
    private readonly FtpClient _ftpClient;
    private bool _disposed;

    public MyFtpClient(MyFtpClientSettings settings)
    {
        _ftpClient = new FtpClient(settings.Host, settings.Port, settings.Username, settings.Password)
        {
            EnableSsl = settings.EnableSsl,
            ConnectTimeout = 10000
        };
    }

    // 封装业务常用的FTP操作
    public async Task UploadFileAsync(string localPath, string remotePath, CancellationToken cancellationToken = default)
    {
        await _ftpClient.ConnectAsync(cancellationToken);
        try
        {
            await _ftpClient.UploadFileAsync(localPath, remotePath, FtpLocalExists.Overwrite, cancellationToken: cancellationToken);
        }
        finally
        {
            await _ftpClient.DisconnectAsync(cancellationToken);
        }
    }

    // 暴露原生客户端供复杂场景使用
    public FtpClient NativeClient => _ftpClient;

    // 实现IDisposable释放资源
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (_disposed) return;
        if (disposing)
        {
            _ftpClient?.Dispose();
        }
        _disposed = true;
    }
}

步骤3:编写IServiceCollection扩展方法

参考AddHttpClient的模式,实现支持单客户端和多命名客户端的注册扩展:

基础版(单FTP服务器)

public static class FtpClientServiceExtensions
{
    public static IServiceCollection AddFtpClient(this IServiceCollection services, IConfiguration configSection)
    {
        // 绑定配置到Options系统,支持热重载
        services.Configure<MyFtpClientSettings>(configSection);
        
        // 注册Scoped生命周期的包装类,和请求周期一致
        services.AddScoped<MyFtpClient>(sp =>
        {
            var settings = sp.GetRequiredService<IOptions<MyFtpClientSettings>>().Value;
            return new MyFtpClient(settings);
        });

        return services;
    }
}

在Program.cs中使用:

// 假设appsettings.json中有"FtpSettings"节点
builder.Services.AddFtpClient(builder.Configuration.GetSection("FtpSettings"));

进阶版(多FTP服务器,命名客户端)

如果需要对接多个FTP服务,可以实现命名客户端模式:

public static class FtpClientServiceExtensions
{
    // 通过代码配置命名客户端
    public static IServiceCollection AddFtpClient(this IServiceCollection services, string clientName, Action<MyFtpClientSettings> configure)
    {
        services.AddOptions<MyFtpClientSettings>(clientName).Configure(configure);
        RegisterNamedClient(services, clientName);
        return services;
    }

    // 通过配置文件绑定命名客户端
    public static IServiceCollection AddFtpClient(this IServiceCollection services, string clientName, IConfiguration configSection)
    {
        services.AddOptions<MyFtpClientSettings>(clientName).Bind(configSection);
        RegisterNamedClient(services, clientName);
        return services;
    }

    private static void RegisterNamedClient(IServiceCollection services, string clientName)
    {
        services.AddScoped(sp =>
        {
            var settingsSnapshot = sp.GetRequiredService<IOptionsSnapshot<MyFtpClientSettings>>();
            var settings = settingsSnapshot.Get(clientName);
            return new MyFtpClient(settings);
        });
    }
}

使用示例:

// 注册两个不同的FTP客户端
builder.Services.AddFtpClient("ServerA", cfg =>
{
    cfg.Host = "ftp.servera.com";
    cfg.Username = "adminA";
    cfg.Password = "passA123";
});

builder.Services.AddFtpClient("ServerB", builder.Configuration.GetSection("Ftp:ServerB"));

注入使用时,通过IOptionsSnapshot获取对应配置:

public class FileSyncService
{
    private readonly MyFtpClient _serverAClient;
    private readonly MyFtpClient _serverBClient;

    public FileSyncService(IOptionsSnapshot<MyFtpClientSettings> settingsSnapshot)
    {
        _serverAClient = new MyFtpClient(settingsSnapshot.Get("ServerA"));
        _serverBClient = new MyFtpClient(settingsSnapshot.Get("ServerB"));
    }
}

方案优势对比你的现有实现

  • 配置解耦:依托.NET Options系统,支持环境变量、配置文件热重载等特性
  • 生命周期可控:Scoped生命周期避免长时间持有连接,减少资源泄漏风险
  • 扩展性强:支持多命名客户端,适配复杂业务场景
  • 可测试性高:通过替换配置或模拟MyFtpClient,轻松实现单元测试

内容的提问来源于stack exchange,提问作者mclemore_oto

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.02 21:31:07