How to stop file transfer if browser is closed/upl

2019-04-10 07:37发布

问题:

I am uploading a file asynchronously with HTML5 in MVC3. If I have a large file, say 1GB in size, and after 50% upload completion I cancel the upload or close the browser, it still saves a 500MB file within the target folder.

How can I handle this problem within the controller and on the client side?

Here is my controller action:

[HttpPost]
public ActionResult Upload(object fileToUpload1)
{
    var fileName = Request.Headers["X-File-Name"];
    var fileSize = Request.Headers["X-File-Size"];
    var fileType = Request.Headers["X-File-Type"];

    Request.SaveAs("D:\\uploadimage\\" + fileName, false);

    if (fileToUpload1 == null)
    {
        return Json(true, JsonRequestBehavior.AllowGet);
    }
    else { return Json(false, JsonRequestBehavior.AllowGet); }

    // return Json(false, JsonRequestBehavior.AllowGet);
}

And here is the Javascript:

// Uploading - for Firefox, Google Chrome and Safari
xhr = new XMLHttpRequest();

// Update progress bar
xhr.upload.addEventListener("progress", uploadProgress, false);

function uploadProgress(evt) {
    if (evt.lengthComputable) {
        var percentComplete = Math.round(evt.loaded * 100 / evt.total);    

        //assign value to prgress bar Div
        var progressBar = document.getElementById("progressBar");

        progressBar.max = evt.total;
        progressBar.value = evt.loaded;
    }
}

// File load event
xhr.upload.addEventListener("load", loadSuccess, false);

function loadSuccess(evt) { 
    $(fileParentDivobj).find(".ImgDiv").find("span").html("uploaded");
    addfile(fileParentDivobj);
}

//handling error
xhr.addEventListener("error", uploadFailed, false);
xhr.addEventListener("abort", uploadCanceled, false);

function uploadFailed(evt) {
    alert("There was an error attempting to upload the file.");
}

function uploadCanceled(evt) {
    alert("The upload has been canceled by the user or the browser dropped the connection.");
}  

xhr.open("POST", "@Url.Action("Upload","Home")", true);

// Set appropriate headers
xhr.setRequestHeader("Cache-Control", "no-cache");
xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
xhr.setRequestHeader("Content-Type", "multipart/form-data");
xhr.setRequestHeader("X-File-Name", file.fileName);
xhr.setRequestHeader("X-File-Size", file.fileSize);
xhr.setRequestHeader("X-File-Type", file.type);
xhr.setRequestHeader("X-File", file);

// Send the file (doh)
xhr.send(file);

回答1:

First, this is not something that should be resolved using any client-side scripts as I don't believe you will be able to make new request when browser is closing and it certainly wouldn't work when connection is interrupted because of network problems.

So I did some digging and I haven't found anything in asp.net that would tell me that request connection was interrupted. However we can check how much data we have received and how much data we should have had received!

public ActionResult Upload()
{
    // I like to keep all application data in App_Data, feel free to change this
    var dir = Server.MapPath("~/App_Data");
    if (!Directory.Exists(dir))
        Directory.CreateDirectory(dir);

    // extract file name from request and make sure it doesn't contain anything harmful
    var name = Path.GetFileName(Request.Headers["X-File-Name"]);
    foreach (var c in Path.GetInvalidFileNameChars())
        name.Replace(c, '-');

    // construct file path
    var path = Path.Combine(dir, name);

    // this variable will hold how much data we have received
    var written = 0;
    try
    {
        using (var output = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None))
        {
            var buffer = new byte[0x1000];
            var read = 0;

            // while there is something to read, write it to output and increase counter
            while ((read = Request.InputStream.Read(buffer, 0, buffer.Length)) > 0)
            {
                output.Write(buffer, 0, read);
                output.Flush();

                written += read;
            }
        }
    }
    finally
    {
        // once finished (or when exception was thrown) check whether we have all data from the request
        // and if not - delete the file
        if (Request.ContentLength != written)
            System.IO.File.Delete(path);
    }

    return Json(new { success = true });
}

Tested with your client-side code using using asp.net dev server and google chrome.

edit: just noticed Chuck Savage has posted this principle in comments earlier, so props to him :)



回答2:

Focusing on Request is the reason for forgetting the Response which can tell if the client is still connected or not (Response.IsClientConnected).
By simply checking is there is something to read, you ignore the case of a possible long (how long?) network delay from the client side.
Use the Chunk and Lukas approach and incorporate the Response.IsClientConnected property and a thread Sleep of your choise in case there is nothing to read but the client is still connected. This way you will exit your read loop earlier if needed without generating a WTF from the client user.