1
 ┌─────────┐      ┌─ ───────────┐      ┌───────────────────────┐
 │ Postman │ ───► │ Web API App │ ───► │ Save file to a folder │
 └─────────┘      └─────────────┘      └───────────────────────┘

In order to simulate, I stream a file to Web API via postman and the API finally saves the file to a folder.

Problem - input.Read throws Maximum request length exceeded. exception.

Question - Could I stream upload a large file without adding maxRequestLength and maxAllowedContentLength in web.config?

In other words, do we have any work around without adding those settings in web.config?

enter image description here

public class ServerController : ApiController
{
    public async Task<IHttpActionResult> Post()
    {
        // Hard-coded filename for testing
        string filePath = string.Format(@"C:\temp\{0:yyyy-MMM-dd_hh-mm-ss}.zip", DateTime.Now);

        int bufferSize = 4096;
        int bytesRead;
        byte[] buffer = new byte[bufferSize];

        using (Stream input = await Request.Content.ReadAsStreamAsync())
        using (Stream output = File.OpenWrite(filePath))
        {
            while ((bytesRead = input.Read(buffer, 0, bufferSize)) > 0)
            {
                output.Write(buffer, 0, bytesRead);
            }
        }

        return Ok();
    }
}
Win
  • 61,100
  • 13
  • 102
  • 181
  • Well, those settings are not application controlled, so the only way to set them is via web.config (or machine.config, see [this MS link](https://support.microsoft.com/en-us/help/295626/prb-cannot-upload-large-files-when-you-use-the-htmlinputfile-server-co)) – Cleptus Oct 18 '18 at 21:38

2 Answers2

2

you cannot do it programmatically. The request length is handled by the HttpWorkerRequest prior the call to the actual HttpHandler. Meaning that a generic handler or a page is executed after the request hit the server and has processed by the corresponding asp.net worker.

You cannot have any control over the maxRequestLength in your page code or an HttpHandler!

If what you need is set max length for specific page you can do it as follows using the tag :

<configuration>
  <location path="yourPage.aspx">
    <system.web>
      <httpRuntime maxRequestLength="2048576" executionTimeout="54000" />
    </system.web>
  </location>
</configuration>
Ivan Fontalvo
  • 433
  • 4
  • 21
  • In other words... you have to set the Length very large for the entire virtual directory/web site/application, and then limit it in other pages to prevent large uploads/DOS situations... meaning it's also often a good idea to have this handler isolated to it's own application, if you can have that much control over the web server. – Joel Coehoorn Oct 18 '18 at 21:55
2

It is tricky but not impossible. The idea: cut the file in pieces on the Client side and send it chunk-wise. On the Server combine chunks. This is working example.

Client:

<input type="file" id="inFile" />
<button id="btnSend" type="button">Upload</button>
<script>
    document.getElementById('btnSend').addEventListener('click', function () {
        var chunkSize = 200; //small for test purpose. Set below limit
        var fu = document.getElementById('inFile');
        if (!fu.files) return;
        var reader = new FileReader();
        reader.onload = function () {
            var bytes = this.result.split('').map(function (b) { return b.charCodeAt(); });
            var xhr = new XMLHttpRequest();
            xhr.onreadystatechange = function () {
                if (this.readyState === 4 && this.status === 200) {
                    var b = bytes.splice(0, chunkSize);
                    if (b.length) {
                        //repeat until EOF
                        xhr.open('POST', 'img-upload.ashx', true);
                        xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
                        xhr.send('fn=' + fu.files[0].name + '&bytes=' + b);
                    }
                }
            };
            xhr.open('POST', 'img-upload.ashx', true);
            xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); //required to use Request.Form on the server
            xhr.send('fn=' + fu.files[0].name + '&bytes=' + bytes.splice(0, chunkSize) + '&chunk=0'); //mark 1st chunk
        }
        reader.readAsBinaryString(fu.files[0]);
    });
</script>

Server:
I used ASP.NET generic handler (.ashx) here.

using System;
using System.Web;
using System.Linq;

public class img_upload : IHttpHandler {

    public void ProcessRequest (HttpContext context) {
        string[] strBytes = ((string)context.Request.Form["bytes"]).Split(',');
        byte[] bytes = strBytes.Select(b => Convert.ToByte(b)).ToArray();
        string fileName = context.Server.MapPath("~/misc/img/" + (string)context.Request.Form["fn"]); //make sure the process has write permission
        System.IO.FileStream fs = null;
        if (context.Request.Form["chunk"] == "0")//first chunk
        {
            fs = new System.IO.FileStream(fileName, System.IO.FileMode.Create);
        }
        else
        {
            fs = new System.IO.FileStream(fileName, System.IO.FileMode.Append);
        }
        fs.Write(bytes, 0, bytes.Length);
        fs.Close();
        context.Response.ContentType = "text/plain"; //or whatever
        context.Response.Write("OK");//or whatever
    }

    public bool IsReusable {
        get {
            return false;
        }
    }
}
Alex Kudryashev
  • 9,120
  • 3
  • 27
  • 36