将文件上传到Google云端硬盘时,多部分主体格式错误

时间:2019-06-22 11:49:29

标签: c# google-drive-api

尝试解决使用其/upload端点将文件上传到Google云端硬盘的问题。即使尝试将简单的纯文本格式上传为文件,我仍然遇到Malformed multipart body.错误。

以下.net c#代码用于创建请求:

string fileName = "test.txt";
string fileContent = "The quick brown fox jumps over the lazy dog";

var fileStream = GenerateStreamFromString(fileContent); // simple text string to Stream conversion
var streamContent = new StreamContent(fileStream);
streamContent.Headers.ContentType = new MediaTypeHeaderValue("text/plain");

var multiPartFormDataContent = new MultipartFormDataContent("not_so_random_boundary");
// rfc2387 headers with boundary
multiPartFormDataContent.Headers.Remove("Content-Type");
multiPartFormDataContent.Headers.TryAddWithoutValidation("Content-Type", "multipart/related; boundary=" + "not_so_random_boundary");
// metadata part
multiPartFormDataContent.Add(new StringContent("{\"name\":\"" + fileName + "\",\"mimeType\":\"text/plain\",\"parents\":[\"" + folder.id + "\"]}", Encoding.UTF8, "application/json"));
// media part (file)
multiPartFormDataContent.Add(streamContent);

var response_UploadFile = await httpClient.PostAsync(string.Format("https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart"), multiPartFormDataContent);

我记录了以下请求:

Method: POST,
RequestUri: 'https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart',
Version: 1.1,
Content: System.Net.Http.MultipartFormDataContent,
Headers: { Authorization: Bearer /*snip*/ Content-Type: multipart/related; boundary=not_so_random_boundary }

具有以下请求内容(已修饰):

--not_so_random_boundary
Content-Type: application/json; charset=utf-8

Content-Disposition: form-data
{"name":"test.txt","mimeType":"text/plain","parents":["/*snip*/"]}

--not_so_random_boundary
Content-Type: text/plain

Content-Disposition: form-data
The quick brown fox jumps over the lazy dog
--not_so_random_boundary--

我花了整整一整天的时间,这使我明白了这一点。我预感这个问题有点愚蠢,但我只是想不通。

有人可以将视线转移到这个位置上吗,也许您可​​以发现我犯了一个错误,这将非常有帮助?

参考:

Send a multipart upload request

RFC 2387

3 个答案:

答案 0 :(得分:1)

我认为multipart/related的请求正文的结构可能不正确。那么如何进行如下修改?

修改后的请求正文:

--not_so_random_boundary
Content-Type: application/json; charset=utf-8
Content-Disposition: form-data; name="metadata"

{"name":"test.txt","mimeType":"text/plain","parents":["/*snip*/"]}
--not_so_random_boundary
Content-Type: text/plain
Content-Disposition: form-data; name="file"

The quick brown fox jumps over the lazy dog
--not_so_random_boundary--
  • 请注意请求正文的换行符。
  • 请为name的每个部分添加Content-Disposition

注意:

  • 现在,我可以确认,当上述修改后的请求正文用作POST方法的https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart的端点时,将创建具有test.txt内容的The quick brown fox jumps over the lazy dog文本文件。

参考文献:

如果这不起作用,我表示歉意。

答案 1 :(得分:1)

由于@Tanaike的建议,我们发现了我的代码存在的问题。

结果虽然没有在文档(或任何代码示例)中未特别提及,但在请求正文的Content-Disposition: form-data; name="metadata"部分中添加StringContent可以带来全部不同。

最终请求可以重写如下:

// sample file (controlled test example)
string fileName = "test.txt";
string fileType = "text/plain";
string fileContent = "The quick brown fox jumps over the lazy dog";
var fileStream = GenerateStreamFromString(fileContent); // test file

// media part (file)
//var fileStream = File.Open(path_to_file, FileMode.Open, FileAccess.Read); // you should read file from disk
var streamContent = new StreamContent(fileStream);
streamContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("form-data");
streamContent.Headers.ContentDisposition.Name = "\"file\"";

// metadata part
var stringContent = new StringContent("{\"name\":\"" + fileName + "\",\"mimeType\":\"" + fileType + "\",\"parents\":[\"" + folder.id + "\"]}", Encoding.UTF8, "application/json");
stringContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("form-data");
stringContent.Headers.ContentDisposition.Name = "\"metadata\"";

var boundary = DataTime.Now.Ticks.ToString(); // or hard code a string like in my previous code
var multiPartFormDataContent = new MultipartFormDataContent(boundary);
// rfc2387 headers with boundary
multiPartFormDataContent.Headers.Remove("Content-Type");
multiPartFormDataContent.Headers.TryAddWithoutValidation("Content-Type", "multipart/related; boundary=" + boundary);
// request body
multiPartFormDataContent.Add(stringContent); // metadata part - must be first part in request body
multiPartFormDataContent.Add(streamContent); // media part - must follow metadata part

var response_UploadFile = await httpClient.PostAsync(string.Format("https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart"), multiPartFormDataContent);

请注意,通常情况下,系统会在StreamContent中添加文件名和内容类型,但Google云端硬盘API会忽略这些标头。这是有意完成的,因为API希望接收具有相关属性的元数据对象。 (以下标头已从上述代码示例中删除,但将保留在此处以供将来参考)

streamContent.Headers.ContentDisposition.FileName = "\"" + fileName + "\"";
streamContent.Headers.ContentType = new MediaTypeHeaderValue(fileType);

请注意,如果您要将文件上传到Google云端硬盘中的子文件夹,则只需指定"parents":["{folder_id}"]属性。

希望这对以后的人有帮助。

答案 2 :(得分:0)

另一种选择是使用 Google .net 客户端库,让它为您处理上传。

// Upload file Metadata
var fileMetadata = new Google.Apis.Drive.v3.Data.File()
    {
    Name = "Test hello uploaded.txt",
    Parents = new List() {"10krlloIS2i_2u_ewkdv3_1NqcpmWSL1w"}
    };

string uploadedFileId;
// Create a new file on Google Drive
await using (var fsSource = new FileStream(UploadFileName, FileMode.Open, FileAccess.Read))
      {
      // Create a new file, with metadata and stream.
      var request = service.Files.Create(fileMetadata, fsSource, "text/plain");
      request.Fields = "*";
      var results = await request.UploadAsync(CancellationToken.None);

      if (results.Status == UploadStatus.Failed)
         {
         Console.WriteLine($"Error uploading file: {results.Exception.Message}");
         }

          // the file id of the new file we created
          uploadedFileId = request.ResponseBody?.Id;
      }

Upload files to google drive