1

I am building a desktop app (WinForms) in C# (VS 2019, .net Framework 4.7.2) which allows the user to select a file, then does some processing on the file, then uploads the file to a Google Drive account and provides the user a direct download link to the file.

I have been testing various sample code found on Google's API docs, StackOverflow, and other sites.

It seems there is a mishmash of V2 and V3 API examples, and I have not yet found a clear example using V3.

I have sort of gotten it to work with the code below, cobbled together from various sources, but there are a few problems:

  1. Larger files don't work (tested 30MB file and nothing happens - no definitive error messages). I need to support files at least 100MB in size.

  2. Every upload throws exceptions, even if the upload completes successfully. (See console output below)

  3. The Progress function does not get called periodically through the upload, so I don't get regular progress updates (i.e. the app just sits there until the upload is done, with no report on progress.)

I'm seeking a complete code snippet to perform a Google Drive upload which:

  1. Includes the Authentication steps
  2. Is written using the V3 API
  3. Allows the upload of large files (100+ MB)
  4. Provides progress feedback
  5. Provides clear success/failure feedback (doesn't throw exceptions unless a failure occurs)
  6. Returns a public shareable link to the uploaded file (i.e. a link that doesn't require the recipient to log into Google in order to download the file)

Here is what I have so far:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using Google.Apis;
using Google.Apis.Auth;
using Google.Apis.Auth.OAuth2;
using Google.Apis.Drive.v3;
using Google.Apis.Services;
using Google.Apis.Util.Store;

namespace FileUploader
{
    public partial class Form2 : Form
    {

        private DriveService service;

        public Form2()
        {
            InitializeComponent();
        }

        private void btnAuth_Click(object sender, EventArgs e)
        {

            string[] scopes = new string[] { DriveService.Scope.Drive,
                                DriveService.Scope.DriveFile,};

            var clientId = "******.apps.googleusercontent.com";      // From https://console.developers.google.com  
            var clientSecret = "******";          // From https://console.developers.google.com  

            // here is where we Request the user to give us access, or use the Refresh Token that was previously stored in %AppData%  
            var credential = GoogleWebAuthorizationBroker.AuthorizeAsync(new ClientSecrets
            {
                ClientId = clientId,
                ClientSecret = clientSecret
            }, scopes,
            Environment.UserName, CancellationToken.None, new FileDataStore("UploaderToken")).Result;
            //Once consent is recieved, your token will be stored locally on the AppData directory, so that next time you wont be prompted for consent.   

            service = new DriveService(new BaseClientService.Initializer()
            {
                HttpClientInitializer = credential,
                ApplicationName = "UploaderApp",

            });
            service.HttpClient.Timeout = TimeSpan.FromMinutes(100);
            //Long Operations like file uploads might timeout. 100 is just precautionary value, can be set to any reasonable value depending on what you use your service for  



            var response = uploadFile(service, txtUpload.Text, "");
            // Third parameter is empty it means it would upload to root directory, if you want to upload under a folder, pass folder's id here.
            //MessageBox.Show("Process completed--- Response--" + response);
            Console.WriteLine("Completed: " + response);

        }



        public Google.Apis.Drive.v3.Data.File uploadFile(DriveService _service, string _uploadFile, string _parent, string _descrp = "Uploaded with .NET!")
        {
            if (System.IO.File.Exists(_uploadFile))
            {
                Google.Apis.Drive.v3.Data.File body = new Google.Apis.Drive.v3.Data.File();
                body.Name = System.IO.Path.GetFileName(_uploadFile);
                body.Description = _descrp;
                body.MimeType = GetMimeType(_uploadFile);
                // body.Parents = new List<string> { _parent };// UN comment if you want to upload to a folder(ID of parent folder need to be send as paramter in above method)
                byte[] byteArray = System.IO.File.ReadAllBytes(_uploadFile);
                System.IO.MemoryStream stream = new System.IO.MemoryStream(byteArray);
                try
                {
                    FilesResource.CreateMediaUpload request = _service.Files.Create(body, stream, GetMimeType(_uploadFile));
                    //request.SupportsTeamDrives = true;
                    // You can bind event handler with progress changed event and response recieved(completed event)
                    request.ProgressChanged += Request_ProgressChanged;
                    request.ResponseReceived += Request_ResponseReceived;
                    request.UploadAsync();
                    return request.ResponseBody;
                }
                catch (Exception e)
                {
                    Console.WriteLine("Error: " + e.Message);
                    return null;
                }
            }
            else
            {
                MessageBox.Show("The file does not exist.", "404");
                return null;
            }
        }

        private static string GetMimeType(string fileName)
        {
            string mimeType = "application/unknown";
            string ext = System.IO.Path.GetExtension(fileName).ToLower();
            Microsoft.Win32.RegistryKey regKey = Microsoft.Win32.Registry.ClassesRoot.OpenSubKey(ext);
            if (regKey != null && regKey.GetValue("Content Type") != null)
                mimeType = regKey.GetValue("Content Type").ToString();
            return mimeType;
        }

        private void Request_ProgressChanged(Google.Apis.Upload.IUploadProgress obj)
        {
            txtProgress.Text += obj.Status + " " + obj.BytesSent;
        }

        private void Request_ResponseReceived(Google.Apis.Drive.v3.Data.File obj)
        {
            if (obj != null)
            {
                Console.WriteLine("File was uploaded sucessfully--" + obj.Id);
                Console.WriteLine("Getting shareable link...");

                // When completed, get shareable link:
                string fileId = obj.Id;
                Google.Apis.Drive.v3.Data.Permission permission = new Google.Apis.Drive.v3.Data.Permission();
                permission.Type = "anyone";
                permission.Role = "reader";
                permission.AllowFileDiscovery = true;

                PermissionsResource.CreateRequest request = service.Permissions.Create(permission, fileId);
                try
                {
                    request.Fields = "*";
                    request.Execute();
                }
                catch (Exception e)
                {
                    Console.WriteLine("Exception: " + e.Message);
                }

                FilesResource.ListRequest listRequest = service.Files.List();
                listRequest.Fields = "*";

                List<Google.Apis.Drive.v3.Data.File> files;
                Google.Apis.Drive.v3.Data.File myFile = null;
                try
                {


                    files = listRequest.Execute().Files.ToList();
                     myFile = files.Single(f => f.Id == fileId);

                }
                catch (Exception e)
                {
                    Console.WriteLine("Exception2: " + e.Message);
                }


                string shareableLink = myFile.WebContentLink;

                Console.WriteLine("Shareable link to file: " + shareableLink);

            }
        }


    }
}

Console output:

Completed: 
File was uploaded sucessfully--[file ID here]
Getting shareable link...
Shareable link to file: https://drive.google.com/a/*******&export=download
Exception thrown: 'System.InvalidOperationException' in System.Windows.Forms.dll
Exception thrown: 'System.InvalidOperationException' in System.Windows.Forms.dll
Exception thrown: 'System.InvalidOperationException' in mscorlib.dll
The thread 0x3d68 has exited with code 0 (0x0).
The thread 0x3d58 has exited with code 0 (0x0).
The thread 0x3d88 has exited with code 0 (0x0).
The thread 0x3d48 has exited with code 0 (0x0).
The thread 0x3d44 has exited with code 0 (0x0).
Ryan Griggs
  • 2,457
  • 2
  • 35
  • 58

2 Answers2

1

Troubleshooting

File size

As per documentation, File Uploads are not guaranteed to succeed. If you file is larger than 5MB, you should upload is as a resumable upload instead. Here is some reference on how to do it.

Exceptions after uploading successfully

Without a stack trace from your exception it's really hard to understand why it is happening. However, the fact that Exception thrown: 'System.InvalidOperationException' in System.Windows.Forms.dll appears after the link to the file, likely means this is not related to the upload, but to the flow after that.

Progress Updates

If you look at the reference I provided you on the first part, you will see a line:

MediaHttpUploader mediaHttpUploader = new MediaHttpUploader(new FileContent("application/json", Paths.get("/path/to/foo.json").toFile()), HTTP_TRANSPORT, credential);
mediaHttpUploader.setProgressListener(uploader -> System.out.println("progress: " + uploader.getProgress()));

This is how you hook your function to update your progress bar into the file upload.

Hope this helps!

ZektorH
  • 2,680
  • 1
  • 7
  • 20
1

Let me mention a possible race condition that might contribute (It's threading, after all, so I can't say for sure).

You're doing a Console.WriteLine on the return value of the uploadFile(…) method, which returns immediately after calling request.UploadAsync(). And that's the thing: it's UploadAsync() and maybe I'm missing it but what I don't see is anything to ensure the completion of the upload task before trying to use the request.ResponseBody member.

So I am curious if you experimented with using the synchronous Upload() method instead, or alternatively blocking with something like request.UploadAsync().Wait().

IVSoftware
  • 5,732
  • 2
  • 12
  • 23