FlipperBizkut
FlipperBizkut

Reputation: 433

Disposing a connection that returns a stream

I am using Renci.SshNet.Sftp to make a connection to an SFTP server. Once connected, I am returning an SftpFileStream of a file found on the server to an action of my controller. The controller actually sends a reader of the SftpFileStream to a service method which parses the stream and does what it needs to with the information.

Here is the question...

When instantiating a new SftpClient, one should use the using keyword to ensure that the connection is disposed of. However, if I do that, then the SftpClient gets disposed before the file can be parsed. To fix this, I remove the using statement, and all is well. But then I have no way of disposing the SftpClient. So what to do?

Here is some code...

Connection Service:

public class SFTPConnectionService
{
    public SftpFileStream GetRemoteFile(string filename)
    {
        // Server credentials
        var host = "host";
        var port = 22;
        var username = "username";
        var password = "password";

        var sftp = new SftpClient(host, port, username, password);

        // Connect to the SFTP server
        sftp.Connect();

        // Read the file in question
        var file = sftp.OpenRead(filename);

        return file;
    }
}

Controller:

public class RemoteFileController : Controller
{
    public ActionResult SFTP()
    {
        var sftpService = new SFTPConnectionService();

        using (var reader = new StreamReader(sftpService.GetRemoteFile(@"path/filename.txt")))
        {
            // Do whatever with the file

            return View("View", ViewModel);
        }
    }
}

So I am using a using block around the StreamReader that reads the file, so that will be disposed of, but that still leaves the SftpClient that needs to be disposed of.

I had thought about copying the SftpFileStream to another stream, and returning that so that I could wrap the SftpClient in a using block, but that seems like a bad idea already, and I also don't know how big the file is, so I really want to stream it from the SFTP server instead of copying it all into memory.

How do I handle this situation?

Upvotes: 1

Views: 2323

Answers (1)

D Stanley
D Stanley

Reputation: 152566

I really want to stream it from the SFTP server instead of copying it all into memory

Well it's all going to get into memory anyway when it gets added to the ViewModel. Here's a few options:

  • Wrap the stream in a class that implements IDisposable and is responsible for disposing of the stream and the client.
  • Don't return a stream but rather return the data from it. Then you can dispose of the stream and the client together.

If you want to make SFTPConnectionService implement IDisposable then you would do something like this:

  • Add class properties for the SFTPConnectionService and StreamReader
  • Add a Dispose method to SFTPConnectionService that calls Dispose on each of them

Something like this:

public class SFTPConnectionService : IDisposable
{

    private SftpClient sftp;
    private SftpFileStream file;

    public SftpFileStream GetRemoteFile(string filename)
    {
        // Server credentials
        var host = "host";
        var port = 22;
        var username = "username";
        var password = "password";

        sftp = new SftpClient(host, port, username, password);

        // Connect to the SFTP server
        sftp.Connect();

        // Read the file in question
        file = sftp.OpenRead(filename);

        return file;
    }

    // Recommended implementation of IDisposable:
    public void Dispose()
    {
        Dispose(true);

        // Use SupressFinalize in case a subclass 
        // of this type implements a finalizer.
        GC.SuppressFinalize(this);   
    }
    protected virtual void Dispose(bool disposing)
    {
        if (!_disposed)
        {
            if (disposing) 
            {
                if(sftp != null)
                     sftp.Dispose();
                if(file != null)
                     file.Dispose();
            }

            // Indicate that the instance has been disposed.
            _disposed = true;   
        }
    }
}

your calling code would then be

using(var sftpService = new SFTPConnectionService())
using(var reader = new StreamReader(sftpService.GetRemoteFile(@"path/filename.txt")))
{
        // Do whatever with the file

        return View("View", ViewModel);
}

Upvotes: 1

Related Questions