.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
相关产品推荐
相关产品推荐

