【问题标题】:Cannot upload to OneDrive using the new SDK无法使用新 SDK 上传到 OneDrive
【发布时间】:2016-09-05 12:26:18
【问题描述】:

我正在将 8.1 应用程序转换为 Windows 10 UWP。该应用使用 OneDrive 进行私有备份和恢复,使用新的 OneDrive SDK,因为 LiveSDK 不适用于 UWP 应用。

我在登录我的 OneDrive、列出文件并下载它们时没有问题(使用 GitHub documentation 中显示的代码,但我尚未成功将文件上传到我的 OneDrive。

我使用的代码是:

string path = "/EpubReader_Backups/" + zipfile.Name;
string s = "";
try
{
    Item uploadedItem = await App.Client
                  .Drive
                  .Root
                  .ItemWithPath(path)
                  .Content
                  .Request()
                  .PutAsync<Item>
                (await zipfile.OpenStreamForReadAsync());
}
catch (Exception ex)
{
    s = ex.Message;
}

再次按照 GitHub 文档中的说明。

调用时,应用开始上传流(大约 30MB),但大约 30 秒后引发异常(不是 OneDriveException,而是正常异常)。

异常消息表明上传已被取消,但未说明原因。

错误消息(来自 mscorlib)是“任务已取消”。错误代码是-2146233029

发生了什么事?我做错了什么?

【问题讨论】:

  • 我在 SDK GitHub 中打开了一个问题,以防这是一个 SDK 问题:github.com/OneDrive/onedrive-sdk-csharp/issues/10。您能否提供有关如何获取 zipfile 对象的更多详细信息?如果异常类型不是 OneDriveException,则很可能 await zipfile.OpenStreamForReadAsync() 是正在抛出的行。
  • 这里的问题看起来有两个方面:我们没有正确捕获客户端异常并将其包装成更有意义的 OneDriveException 并且 HttpClient 正在超时。简单上传在服务上可能需要一段时间,因此需要增加超时时间。上面的问题是跟踪修复的正确方法。

标签: live-sdk


【解决方案1】:

在等待 SDK 实施的同时,我将一个不可恢复的分块上传到 OneDrive

using Microsoft.OneDrive.Sdk;
using Newtonsoft.Json;
using SimpleAuthenticationProvider;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;

namespace Microsoft.OneDrive.Sdk
{

    public static class Extend
    {
        public static async Task<Item> UploadChunks(this OneDriveClient client, string folder, string fileName, Stream stream, int chunkSize, LiveToken token)
        {
            var session = await client.Drive.Root.ItemWithPath(folder + "/" + fileName).CreateSession(new ChunkedUploadSessionDescriptor() { Name = fileName }).Request().PostAsync();
            using (OneDriveChunkUpload chunks = new OneDriveChunkUpload(stream, session.UploadUrl, chunkSize, token))
            {
                return await client.Drive.Items[chunks.Upload().id].Request().GetAsync();
            }
        }
    }
    public class LiveToken
    {
        public string token_type { get; set; }
        public int expires_in { get; set; }
        public string scope { get; set; }
        public string access_token { get; set; }
        public string refresh_token { get; set; }
        public string user_id { get; set; }
    }

    public class itemCreated
    {
        public string id { get; set; }
        public string name { get; set; }
        public int size { get; set; }
    }

    class responseChunk
    {
        public string uploadUrl { get; set; }
        public DateTime expirationDateTime { get; set; }
        public List<string> nextExpectedRanges { get; set; }
        public List<Range> Ranges
        {
            get
            {
                List<Range> ret = new List<Range>();
                foreach (var r in nextExpectedRanges)
                {
                    string[] parsed = r.Split('-');
                    Range ra = new Range(parsed[0], parsed[1]);
                    ret.Add(ra);
                }
                return ret;
            }
        }
    }
    class Range
    {
        public Range() { }
        public Range(string start, string end)
        {
            Start = int.Parse(start);
            if (!string.IsNullOrEmpty(end))
                End = int.Parse(end);
        }
        public int Start { get; set; }
        public int? End { get; set; }
    }
    public class OneDriveChunkUpload : IDisposable
    {
        Stream source;
        string Url;
        int ChunkSize;
        responseChunk lastResponse;
        LiveToken Token;
        public itemCreated item = null;
        public OneDriveChunkUpload(Stream str, string url, int chunkSize, LiveToken token)
        {
            Token = token;
            source = str;
            Url = url;
            ChunkSize = chunkSize;
            lastResponse = new responseChunk() { nextExpectedRanges = new List<string>() { "0-" + (str.Length -1).ToString() } };
        }

        public itemCreated Upload()
        {
            long position = 0;
            while (lastResponse != null)
            {
                Range r = new Range();
                r.Start = lastResponse.Ranges[0].Start;
                r.End = (int)Math.Min(((lastResponse.Ranges[0].End ?? int.MinValue) + 1) - r.Start, ChunkSize);
                r.End += r.Start -1;
                byte[] buffer = new byte[r.End.Value - r.Start + 1];
                source.Position = r.Start;
                //source.Seek(r.Start, SeekOrigin.Begin);
                position += source.Read(buffer, 0, buffer.Length);
                Put(buffer, r);
            }
            return item;
        }

        void Put(byte[] bytes, Range r)
        {

            WebRequest req = HttpWebRequest.Create(Url);
            req.Method = "PUT";
            req.Headers.Add(HttpRequestHeader.Authorization, string.Format("bearer {0}", Token.access_token));
            req.ContentLength = bytes.Length;
            string ContentRange = string.Format("bytes {0}-{1}/{2}", r.Start, r.End.Value, source.Length);
            req.Headers.Add(HttpRequestHeader.ContentRange, ContentRange);
            Stream requestStream = req.GetRequestStream();
            requestStream.Write(bytes, 0, bytes.Length);
            HttpWebResponse res = null;
            try
            {
                res = (HttpWebResponse)req.GetResponse();
            }
            catch (Exception ex)
            {
                throw ex;
            }
            string responseBody = null;

            using (StreamReader sr = new StreamReader(res.GetResponseStream(), Encoding.UTF8))
            {
                responseBody = sr.ReadToEnd();
            }
            if (res.StatusCode == HttpStatusCode.Accepted)
            {
                lastResponse = JsonConvert.DeserializeObject<responseChunk>(responseBody);
            }
            else if (res.StatusCode == HttpStatusCode.Created)
            {
                lastResponse = null;
                item = JsonConvert.DeserializeObject<itemCreated>(responseBody);
            }
            else
            {
                throw new Exception("bad format");
            }
        }

        public void Dispose()
        {
            WebRequest req = HttpWebRequest.Create(Url);
            req.Method = "DELETE";
            WebResponse res = req.GetResponse();
        }
    }

}

【讨论】:

    【解决方案2】:

    zipfile 对象是使用 Framework 4.5 和 4.6 ZipArchiveEntry 类创建的简单 zip,代码:

            public static async Task<StorageFile> ZipFileFromFiles(List<StorageFile> files)
            {
                DatabaseUtilities.CloseDatabases();
                using (MemoryStream zipStream = new MemoryStream())
                {
                    using (ZipArchive zip = new ZipArchive(zipStream, ZipArchiveMode.Create))
                    {
                        foreach (StorageFile file in files)
                        {
                            byte[] data;
                            ZipArchiveEntry entry = zip.CreateEntry(file.Name);
                            using (Stream zipFile = entry.Open())
                            {
                                data = await ReadFileBytes(file);
                                zipFile.Write(data, 0, data.Length);
                            }
                        }
    
                        await zipStream.FlushAsync();
                    }
    
                    byte[] zipfile = zipStream.ToArray();
                    String ss = "Backup from ";
                    ss += DateTime.Now.Year.ToString() + "-";
                    ss += DateTime.Now.Month.ToString("D2") + "-";
                    ss += DateTime.Now.Day.ToString("D2") + " ";
                    ss += DateTime.Now.Hour.ToString("D2") + "H";
                    ss += DateTime.Now.Minute.ToString("D2") + "M";
                    ss += DateTime.Now.Second.ToString("D2") + "S";
                    await WriteFile(App.Folder, ss + ".zip", zipfile);
                    return await App.Folder.GetFileAsync(ss + ".zip");
                }
            }

    此代码在应用程序的 8.1 版本中运行良好,它还在 Windows 10 版本中创建了正确的 zip 文件(我已经单独检查了生成的 zip 文件)。

    是的,我同意你的看法,可能是超时报错了。

    虽然正确报告超时很重要,但我认为如果要对 Windows 应用商店应用程序开发人员有用,必须将一些功能添加到 SDK:

    1. 允许为上传文件设置超时
    2. 允许向应用报告下载/上传进度

    虽然这些功能似乎存在于 REST Api 中,但它们在 c# SDK 中却是出了名的缺失(或者可能几乎不存在的文档没有提及它们)。因此,用户体验相当令人不安,进度环不停地转动,而没有任何进度信息传达给应用用户。

    PS 我已按如下方式修改了 try 块,以测试 zip 流的等待是否导致错误。 现在的尝试是: 尝试 { 流 strm = 等待 zipfile.OpenStreamForReadAsync(); 项目上传项目 = 等待应用 。客户 。驾驶 。根 .ItemWithPath(路径) 。内容 。要求() .PutAsync(strm); }

    await zipfile.OpenStreamForReadAsync();语句正确执行,不会引发异常;导致问题的语句确实是将流上传到OneDrive。

    【讨论】:

    • 就上传进度而言,SDK目前只提供简单的上传(通过单个PUT动作上传),没有暴露任何进度功能。目的是在更高版本的 SDK 中添加分块上传功能,以便进行进度报告。
    【解决方案3】:

    我已经确定问题出在 sdk 上传代码内部超时。 我已经从原来的 70Mb 逐渐减小了 zipfile 的大小,直到大小达到 17Mb 和 18Mb 之间才会给出错误;在此之下,文件上传没有任何错误。 我正在使用具有最大 2Mbit 上传能力的 ADSL 连接,因此这应该使开发团队能够建立所需的超时数量。 然而,我必须说,在我看来,UWP 应用程序应该在平板电脑和手机以及 PC 上运行,其中许多平板电脑,当然还有手机将有一个 SIM 卡,并且能够上传和下载数据。移动连接(街上很少有光纤连接)。因此,要么将超时值留给开发人员,要么在包中设置以平均连接速度 (1Mb?) 上传最大可用文件大小 (100Mb?) 的最大值。

    我无意批评 SDK 开发团队,但在我看来,在 Windows 10 几个月后,SDK 的准备和文档几乎没有考虑过,而且它目前的形式还很原始已被释放。等待更好的版本意味着应用不会发布到商店...

    【讨论】:

      【解决方案4】:

      我知道这是一个令人沮丧的限制,我们正在努力解决这个问题。不幸的是,我们目前仅限于 HttpClient 的功能,它不允许任何精细的超时控制。目前最适合您的解决方案是最有可能实现resumable upload,特别是如果您想支持大于 100 mb 的文件。

      SDK 现在确实可以帮助处理这些组件。创建分块上传会话:

      var uploadSession = await client
          .Drive
          .Root
          .ItemWithPath(path)
          .CreateSession(sessionDescriptor)
          .Request()
          .PostAsync();
      

      您可以从那里使用uploadSession.UploadUrl 上传您的个人块。 await client.AuthenticationProvider.AppendAuthHeaderAsync(httpRequestMessage) 会将身份验证标头附加到每个块请求中,因此您不必处理身份验证。

      我们正在努力将其构建到 SDK 中,因此开发人员不必自己实现它,但它应该有助于解决您的场景。

      【讨论】:

      • 是否有 C# 代码示例可用于使用 OneDrive SDK 将大文件上传到 OneDrive?我有这个问题,我在这里发布了一个关于 SO 的问题:stackoverflow.com/questions/36551048/… 谢谢!
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-04-16
      • 1970-01-01
      • 1970-01-01
      • 2015-06-15
      相关资源
      最近更新 更多