C#-将文件上传到Google云端硬盘(Google云端硬盘API)

时间:2019-11-14 14:56:16

标签: c# .net file-upload google-drive-api

我正在用C#(VS 2019,.net Framework 4.7.2)构建一个桌面应用程序(WinForms),允许用户选择一个文件,然后对该文件进行一些处理,然后将该文件上传到Google云端硬盘帐户,并向用户提供文件的直接下载链接。

我一直在测试在Google's API docsStackOverflowother sites上找到的各种示例代码。

似乎有很多V2和V3 API示例混搭,而我还没有找到使用V3的清晰示例。

我已经理解它可以与下面的代码一起使用,这些代码从各种来源整理而来,但是存在一些问题:

  1. 较大的文件不起作用(经过测试的30MB文件,没有任何反应-否 明确的错误消息)。我需要支持至少100MB的文件。

  2. 即使上载成功完成,每个上载也会引发异常。 (请参见下面的控制台输出)

  3. “进度”功能不会在上传过程中定期调用,因此我不会定期获取进度更新信息(即,应用程序一直坐在那里直到 上传完成,没有进度报告。)

我正在寻找完整的代码段来执行Google云端硬盘上传,

  1. 包括身份验证步骤
  2. 使用V3 API编写
  3. 允许上传大文件(超过100 MB)
  4. 提供进度反馈
  5. 提供明确的成功/失败反馈(除非发生故障,否则不会引发异常)
  6. 返回到共享文件的公共共享链接(即不需要收件人登录Google即可下载文件的链接)

这是我到目前为止所拥有的:

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);

            }
        }


    }
}

控制台输出:

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).

2 个答案:

答案 0 :(得分:1)

问题排查

文件大小

根据documentation,不能保证文件上传成功。如果文件大于5MB,则应改为upload is as a resumable upload。这是一些reference的用法。

上传成功后出现异常

如果没有来自异常的堆栈跟踪,那么很难理解为什么会发生。但是,Exception thrown: 'System.InvalidOperationException' in System.Windows.Forms.dll出现在指向文件的链接之后的事实,可能意味着这与上载无关,而是与之后的流有关。

进度更新

如果您查看reference I provided you on the first part,则会看到一行:

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()));

这是您挂钩函数以将进度条更新为文件上传的方式。

希望这会有所帮助!

答案 1 :(得分:1)

让我提到可能可能造成的竞争状况(毕竟是线程,所以我不能肯定地说)。

您正在对uploadFile(…)方法的返回值进行Console.WriteLine,该方法在调用request.UploadAsync()后立即返回 。就是这样:它是Upload Async (),也许我丢失了它,但是我看不到的是可以确保在尝试使用请求之前完成上传任务的任何东西。 。

因此,我很好奇您是否尝试使用同步Upload()方法,或者尝试使用诸如request.UploadAsync()。Wait()之类的方法进行阻止。