I have a class which consumes Renci.SshNet's SftpClient
which implements BaseClient, ISftpClient
.
So, my constructor looks like:
public CollendaSftpClient(
CollendaCsvWriter collendaCsvWriter,
CollendaSftpConfig collendaSftpConfig,
ILogger<CollendaSftpClient> logger,
ISftpClient sftpClient
)
{
_collendaCsvWriter = collendaCsvWriter ?? throw new ArgumentNullException(nameof(collendaCsvWriter));
_collendaSftpConfig = collendaSftpConfig ?? throw new ArgumentNullException(nameof(collendaSftpConfig));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_sftpClient = sftpClient ?? throw new ArgumentNullException(nameof(sftpClient));
}
Annoyingling, ISftpClient
does not include either the Connect()
or Disconnect()
methods, both of which are coming from BaseClient
and included in IBaseClient
(which BaseClient
implements.
Thus, I have an upload method which looks like:
private void Upload(MemoryStream memoryStream)
{
_logger.InterpolatedInformation($"Attempting to upload to {_collendaSftpConfig.Host:SftpHost}");
try
{
SftpClient? sftpClient = _sftpClient as SftpClient;
if (sftpClient != default)
{
sftpClient.Connect();
}
string filePath = _collendaSftpConfig?.FilePath
?.InsertTimestamp()
?? throw new InvalidOperationException("CollendaSftpConfig configuration is missing FilePath.");
// Renci.SshNet's Sftp Client seems to have some async support, but it seems much more complicated to consume.
// There is no clear benefit to using it at this time.
_sftpClient.UploadFile(memoryStream, filePath);
if (sftpClient != default)
{
sftpClient.Disconnect();
}
}
catch (Exception ex)
{
CollendaSftpException sftpException = new($"Failed to upload to {_collendaSftpConfig.Host:SftpHost}", ex);
_logger.InterpolatedError(sftpException, $"Upload failure: {ex.Message:ErrorMessage}");
throw sftpException;
}
}
Needing to check whether I've injected an instance of something else looks ugly and not especially testable... Nor does it seem like a great idea to add it to my services and inject it twice.
Short of asking Renci to fix their interace, what is the best way to deal with this?