Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Send and receive large file over streams in ASP.NET Web Api C#

I'm working on a project where I need to send large audio files via streams from a client to a server. I'm using the ASP.NET Web Api to communicate between client and server. My client has a "SendFile" method which I believe works fine, but I don't know how to make my server receive the data I'm sending via a stream. My client code looks like this so far:

 private const int MAX_CHUNK_SIZE = (1024 * 5000);
    private HttpWebRequest webRequest = null;
    private FileStream fileReader = null;
    private Stream requestStream = null;

    public bool SendAudio(string uri, string file)
    {
        byte[] fileData;
        fileReader = new FileStream(file, FileMode.Open, FileAccess.Read);
        webRequest = (HttpWebRequest)WebRequest.Create(uri);
        webRequest.Method = "POST";
        webRequest.ContentLength = fileReader.Length;
        webRequest.Timeout = 600000;
        webRequest.Credentials = CredentialCache.DefaultCredentials;
        webRequest.AllowWriteStreamBuffering = false;
        requestStream = webRequest.GetRequestStream();

        long fileSize = fileReader.Length;
        long remainingBytes = fileSize;
        int numberOfBytesRead = 0, done = 0;

        while (numberOfBytesRead < fileSize)
        {
            SetByteArray(out fileData, remainingBytes);
            done = WriteFileToStream(fileData, requestStream);
            numberOfBytesRead += done;
            remainingBytes -= done;
        }
        fileReader.Close();
        return true;
    }

    public int WriteFileToStream(byte[] fileData, Stream requestStream)
    {
        int done = fileReader.Read(fileData, 0, fileData.Length);
        requestStream.Write(fileData, 0, fileData.Length);

        return done;
    }

    private void SetByteArray(out byte[] fileData, long bytesLeft)
    {
        fileData = bytesLeft < MAX_CHUNK_SIZE ? new byte[bytesLeft] : new byte[MAX_CHUNK_SIZE];
    }

My server looks like this:

    [HttpPost]
    [ActionName("AddAudio")]
    public async Task<IHttpActionResult> AddAudio([FromUri]string name)
    {
        try
        {
            isReceivingFile = true;
            byte[] receivedBytes = await Request.Content.ReadAsByteArrayAsync();
            if (WriteAudio(receivedBytes, name) == true)
            {
                isReceivingFile = false;
                return Ok();
            }
            else
            {
                isReceivingFile = false;
                return BadRequest("ERROR: Audio could not be saved on server.");
            }
        }
        catch (Exception ex)
        {
            isReceivingFile = false;
            return BadRequest("ERROR: Audio could not be saved on server.");
        }
    }

    public bool WriteAudio(byte[] receivedBytes, string fileName)
    {
        string file = Path.Combine(@"C:\Users\username\Desktop\UploadedFiles", fileName);
        using (FileStream fs = File.Create(file))
        {
            fs.Write(receivedBytes, 0, receivedBytes.Length);
        }
        return true;
    }

The server code has the original code I wrote for it, before deciding to try and make it work with streams. The server code still works if I send a small file (under 30 MB), but if I send a large file my server gets a "outofmemoryexception". I can't figure out how to make the server take in the data via a stream. In my search for solutions I've come across a lot of examples with sockets and TCPClient, but that's not how we want to do it on this project. Can anybody help, please?

like image 729
Tobiuo Avatar asked Apr 28 '17 11:04

Tobiuo


1 Answers

if I send a large file my server gets a "outofmemoryexception"

Well, it's reading the entire stream into memory right here:

byte[] receivedBytes = await Request.Content.ReadAsByteArrayAsync();

What you want to do is copy the stream from one location to another, without loading it all into memory at once. Something like this should work:

[HttpPost]
[ActionName("AddAudio")]
public async Task<IHttpActionResult> AddAudio([FromUri]string name)
{
  try
  {
    string file = Path.Combine(@"C:\Users\username\Desktop\UploadedFiles", fileName);
    using (FileStream fs = new FileStream(file, FileMode.Create, FileAccess.Write,
        FileShare.None, 4096, useAsync: true))
    {
      await Request.Context.CopyToAsync(fs);
    }
    return Ok();
  }
  catch (Exception ex)
  {
    return BadRequest("ERROR: Audio could not be saved on server.");
  }
}
like image 66
Stephen Cleary Avatar answered Oct 02 '22 16:10

Stephen Cleary