Chet at C2IT
Chet at C2IT

Reputation: 547

Large video files failing to upload to a WebApi via WebClient.UploadFileTaskAsync

I'm building a iOS (and later Android) app with Xamarin that allows the user to record a video and upload it to a server using POST to a Web API I have in place.

When I run the application on a device in debug mode, I can see the progress of the file being uploaded but it eventually stops before the file is completely uploaded. However, it throws no exceptions, and the app simply exits. Nothing in the Application Output, nothing in the iOS Log, and no errors in the debugger. It's just gone.

I've used Fiddler to send a similar sized file via a POST to the same Web API endpoint and it works fine. I can't upload anything over roughly 20MB via the app but can successfully upload files 30MB, 50MB, and bigger with Fiddler.

My Web.Config file is set to allow 100MB file uploads in both RequestFiltering and HttpRuntime.

Is there anywhere else I can look to see what's causing this crash? From what I've tested, I seem to have isolated it to the WebClient.UploadFileTaskAsync method call, and it seems to be about 21 MB where the crash starts happening. Files > 21 MB fail anywhere from 10-20MB along the upload, while a 20MB file will make it all the way to 20MB (further along than where the larger file fails).

Here is my file uploader dependency service:

[assembly: Dependency (typeof (FileUploader_iOS))]
namespace GbrApps.iOS
{
public class FileUploader_iOS: IFileUploader
{
    public FileUploader_iOS ()
    {
    }
    public async Task<GbrAppVideo> UploadFileAsync(string FileName, int VideoId)
    {
        try
        {
        //Prepare to make a client ot the API
        WebClient client = new WebClient ();

        client.UploadProgressChanged += (object sender, UploadProgressChangedEventArgs e) => 
        {
            //Upload progress changed as you see fit.
            Console.WriteLine("Progress: {0}, Sent {1}, Total {2}",e.ProgressPercentage,e.BytesSent,e.TotalBytesToSend);
            GlobalResources.UploadProgress = e.ProgressPercentage;
        };

        //Get the file type, because the receiving end receives a generic "bodypart" type of file.
        string[] splitFileName = FileName.Split('.');
        string FileType = splitFileName.Last ().ToLower ();

        //Prep the file upload URL
        //Make sure your receiving end can handle big attachments if you're sending photos or video.
        //ASP.NET can only handle 4MB files by 
        Uri destination = new Uri(string.Format("http://####.com/api/videos?VideoId={0}&FileType={1}&OS=IOS",VideoId,FileType));
        Console.WriteLine ("Uploading to " + destination.ToString ());

        //Send the file and wait for a response asyncronously
        byte[] byteResponse = await client.UploadFileTaskAsync(destination,FileName);
        string response = System.Text.Encoding.UTF8.GetString (byteResponse);

        //Parse the response as a JSON object
        GbrAppVideo result = JsonConvert.DeserializeObject<GbrAppVideo>(response);
        Console.WriteLine (string.Format ("Upload completed: {0}", result));

        //Delete the local file since we don't need it anymore
        if (result.VideoId >0) {
            File.Delete (FileName);
        }

        //Let the system know the video is done
        GlobalResources.activeVideo = result;

        //Return the uploader result object so code can continue and know what to do.
        return result;
            }
        catch (Exception ex)
        {
            Debug.WriteLine(ex.ToString());
            GlobalResources.activeVideo = new GbrAppVideo();
            return GlobalResources.activeVideo;
        }
    }
}
}

EDIT: I figured out that I could test this on my simulator by uploading a larger sample video file than the very small one I used as a placeholder when the camera isn't available. The app still crashes (not in the debugger, just completely locks up) but I get this uncaught exception (even though I have a try/catch block):

System.Net.WebException: An exception occurred during a WebClient request.
  at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw () [0x0000c] in /Users/builder/data/lanes/3539/f37444ae/source/maccore/_build/Library/Frameworks/Xamarin.iOS.framework/Versions/git/src/mono/external/referencesource/mscorlib/system/runtime/exceptionservices/exceptionservicescommon.cs:143 
  at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess (System.Threading.Tasks.Task task) [0x00047] in /Users/builder/data/lanes/3539/f37444ae/source/maccore/_build/Library/Frameworks/Xamarin.iOS.framework/Versions/git/src/mono/external/referencesource/mscorlib/system/runtime/compilerservices/TaskAwaiter.cs:187 
  at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification (System.Threading.Tasks.Task task) [0x0002e] in /Users/builder/data/lanes/3539/f37444ae/source/maccore/_build/Library/Frameworks/Xamarin.iOS.framework/Versions/git/src/mono/external/referencesource/mscorlib/system/runtime/compilerservices/TaskAwaiter.cs:156 
  at System.Runtime.CompilerServices.TaskAwaiter.ValidateEnd (System.Threading.Tasks.Task task) [0x0000b] in /Users/builder/data/lanes/3539/f37444ae/source/maccore/_build/Library/Frameworks/Xamarin.iOS.framework/Versions/git/src/mono/external/referencesource/mscorlib/system/runtime/compilerservices/TaskAwaiter.cs:128 
  at System.Runtime.CompilerServices.TaskAwaiter`1[TResult].GetResult () [0x00000] in /Users/builder/data/lanes/3539/f37444ae/source/maccore/_build/Library/Frameworks/Xamarin.iOS.framework/Versions/git/src/mono/external/referencesource/mscorlib/system/runtime/compilerservices/TaskAwaiter.cs:357 
  at GbrApps.iOS.FileUploader_iOS+<UploadFileAsync>c__async0.MoveNext () [0x00132] in /Users/chetcromer/Projects/GbrApp/GbrApps/iOS/gbrApi/FileUploader_iOS.cs:52 

This references this line of code, which is where the upload happens...

byte[] byteResponse = await client.UploadFileTaskAsync(destination,FileName);

EDIT:

I tried changing my upload line of code to not run async and it runs OK, but I now have to wait for the upload to finish before continuing on in the app (not acceptable):

byte[] byteResponse = client.UploadFile(destination, FileName);

I'm wondering if I have a timeout happening, even though the documentation I'm reading shows that I'd have to call and Abort method, and that there is no auto-timeout for the async call... but it sure seems like that's what's happening.

EDIT: I got this working by moving to UploadFile instead of UploadFileTaskAsync and wrapping the entire thing inside an await Task.Run. I don't get access to the upload progress event anymore, but it does do the task. I'd like to go back to the original method if I can, but until I find out what's causing the exception I can't get trapped, this works for me with no user impact.

await Task.Run(() =>
            {
                byte[] byteResponse = client.UploadFile(destination, FileName);
                string response = System.Text.Encoding.UTF8.GetString(byteResponse);

                //Parse the response as a JSON object
                result = JsonConvert.DeserializeObject<GbrAppVideo>(response);
                Console.WriteLine(string.Format("Upload completed: {0}", result));

                //Delete the local file since we don't need it anymore
                if (result.VideoId > 0)
                {
                    File.Delete(FileName);
                }

                //Let the system know the video is done
                GlobalResources.activeVideo = result;
            }

Upvotes: 0

Views: 1317

Answers (1)

Chet at C2IT
Chet at C2IT

Reputation: 547

This isn't the real answer I was looking for, but it works. I'd still like to get UploadFileTaskAsync working, but it seems to be timing out, so I wrapped UploadFile() in a Task.Run() to get it working (without a progress indicator, however):

I got this working by moving to UploadFile instead of UploadFileTaskAsync and wrapping the entire thing inside an await Task.Run. I don't get access to the upload progress event anymore, but it does do the task. I'd like to go back to the original method if I can, but until I find out what's causing the exception I can't get trapped, this works for me with no user impact.

await Task.Run(() =>
        {
            byte[] byteResponse = client.UploadFile(destination, FileName);
            string response = System.Text.Encoding.UTF8.GetString(byteResponse);

            //Parse the response as a JSON object
            result = JsonConvert.DeserializeObject<GbrAppVideo>(response);
            Console.WriteLine(string.Format("Upload completed: {0}", result));

            //Delete the local file since we don't need it anymore
            if (result.VideoId > 0)
            {
                File.Delete(FileName);
            }

            //Let the system know the video is done
            GlobalResources.activeVideo = result;
        }

Upvotes: 3

Related Questions