21

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?

4
  • Have you tried sending file chunk by chunk from client to server whenever it's over 30MB? Commented Apr 28, 2017 at 11:24
  • That's what I'm doing right now. I have a MAX_CHUNK_SIZE set to 5 MB, so the "WriteFileToStream" method only gets 5 MB of data at a time. Commented Apr 28, 2017 at 11:45
  • have you increased the maxAllowedContentLength in your web.config? Commented Apr 28, 2017 at 11:48
  • Yes, I did that too: <requestLimits maxAllowedContentLength="2147483647"/> Commented Apr 28, 2017 at 12:38

1 Answer 1

34

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.Content.CopyToAsync(fs);
    }
    return Ok();
  }
  catch (Exception ex)
  {
    return BadRequest("ERROR: Audio could not be saved on server.");
  }
}
Sign up to request clarification or add additional context in comments.

8 Comments

Yes, now it works exactly as it should. I had no idea you could do it this way. Thanks a lot!
Hi @Stephen Cleary , I have a question, Is it work for more than 4 GB files?
@Esi: I haven't tried it, but I expect it should work.
Hi @StephenCleary. Is there a way I could use this code to target .NET framework 4.0 ? I am unable to upgrade to 4.5 on the production server.
@Saket: You'd have to write to an output stream. E.g., PushStreamContent on ASP.NET Classic or a custom FileCallbackResult on ASP.NET Core.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.