全部产品
存储与CDN 数据库 安全 应用服务 数加·人工智能 数加·大数据基础服务 互联网中间件 视频服务 开发者工具 解决方案 物联网
对象存储 OSS

上传文件

更新时间:2017-06-12 15:24:17

在OSS中,用户操作的基本数据单元是文件(Object)。单个文件最大允许大小根据上传数据方式不同而不同,Put Object方式最大不能超过5GB, 使用multipart上传方式文件大小不能超过48.8TB。

简单的上传

上传指定字符串:

  1. using System.Text;
  2. using Aliyun.OSS;
  3. // 初始化OssClient
  4. var client = new OssClient(endpoint, accessKeyId, accessKeySecret)
  5. try
  6. {
  7. string str = "a line of simple text";
  8. byte[] binaryData = Encoding.ASCII.GetBytes(str);
  9. MemoryStream requestContent = new MemoryStream(binaryData);
  10. client.PutObject(bucketName, key, requestContent);
  11. Console.WriteLine("Put object succeeded");
  12. }
  13. catch (Exception ex)
  14. {
  15. Console.WriteLine("Put object failed, {0}", ex.Message);
  16. }

说明: 完整代码参考:GitHub

上传指定的本地文件

  1. using Aliyun.OSS;
  2. // 初始化OssClient
  3. var client = new OssClient(endpoint, accessKeyId, accessKeySecret);
  4. try
  5. {
  6. string fileToUpload = "your local file path";
  7. client.PutObject(bucketName, key, fileToUpload);
  8. Console.WriteLine("Put object succeeded");
  9. }
  10. catch (Exception ex)
  11. {
  12. Console.WriteLine("Put object failed, {0}", ex.Message);
  13. }

说明: 完整代码参考GitHub

上传文件并且带MD5校验

  1. using Aliyun.OSS;
  2. using Aliyun.OSS.util;
  3. // 初始化OssClient
  4. var client = new OssClient(endpoint, accessKeyId, accessKeySecret);
  5. try
  6. {
  7. string fileToUpload = "your local file path";
  8. string md5;
  9. using (var fs = File.Open(fileToUpload, FileMode.Open));
  10. {
  11. md5 = OssUtils.ComputeContentMd5(fs, fs.Length);
  12. }
  13. var objectMeta = new ObjectMetadata
  14. {
  15. ContentMd5 = md5
  16. };
  17. client.PutObject(bucketName, key, fileToUpload, objectMeta);
  18. Console.WriteLine("Put object succeeded");
  19. }
  20. catch (Exception ex)
  21. {
  22. Console.WriteLine("Put object failed, {0}", ex.Message);
  23. }

说明:

  • 完整代码参考GitHub
  • 为了保证SDK发送的数据和OSS服务端接收到的数据一致,可以在ObjectMeta中增加Content-Md5值,这样服务端就会使用这个MD5值做校验。
  • 使用Md5时,性能会有所损失。

上传文件带Header

带标准Header

OSS服务允许用户自定义文件的Http Header。下面代码为文件设置了过期时间:

  1. using Aliyun.OSS;
  2. // 初始化OssClient
  3. var client = new OssClient(endpoint, accessKeyId, accessKeySecret);
  4. try
  5. {
  6. using (var fs = File.Open(fileToUpload, FileMode.Open))
  7. {
  8. var metadata = new ObjectMetadata();
  9. metadata.ContentLength = fs.Length;
  10. metadata.ExpirationTime = DateTime.Parse("2015-10-12T00:00:00.000Z");
  11. client.PutObject(bucketName, key, fs, metadata);
  12. Console.WriteLine("Put object succeeded");
  13. }
  14. }
  15. catch (Exception ex)
  16. {
  17. Console.WriteLine("Put object failed, {0}", ex.Message);
  18. }

说明:

  • 完整代码参考GitHub
  • .NET SDK支持的Http Header:Cache-Control 、 Content-Disposition 、Content-Encoding 、 Expires、Content-Type等 。
  • 它们的相关介绍见 RFC2616

带自定义Header

OSS支持用户自定义元信息来对文件进行描述。比如:

  1. using Aliyun.OSS;
  2. // 初始化OssClient
  3. var client = new OssClient(endpoint, accessKeyId, accessKeySecret);
  4. try
  5. {
  6. using (var fs = File.Open(fileToUpload, FileMode.Open))
  7. {
  8. var metadata = new ObjectMetadata();
  9. metadata.UserMetadata.Add("name", "my-data");
  10. metadata.ContentLength = fs.Length;
  11. client.PutObject(bucketName, key, fs, metadata);
  12. Console.WriteLine("Put object succeeded");
  13. }
  14. }
  15. catch (Exception ex)
  16. {
  17. Console.WriteLine("Put object failed, {0}", ex.Message);
  18. }

说明:

  • 完整代码参考GitHub
  • 在上面代码中,用户自定义了一个名字为“name”,值为“my-data”的元信息。
  • 当用户下载此文件的时候,此元信息也可以一并得到。
  • 一个文件可以有多个类似的参数,但所有的user meta总大小不能超过2KB。
  • 使用上述方法上传最大文件不能超过5G。如果超过可以使用MutipartUpload上传。
  • user meta的名称大小写不敏感,比如用户上传文件时,定义名字为“Name”的meta,在表头中存储的参数为:“x-oss-meta-name”,所以读取时读取名字为“name”的参数即可。
  • 但如果存入参数为“name”,读取时使用“Name”读取不到对应信息,会返回“null”。

创建模拟文件夹

OSS服务是没有文件夹这个概念的,所有元素都是以文件来存储。但给用户提供了创建模拟文件夹的方式,如下代码:

  1. using Aliyun.OSS;
  2. // 初始化OssClient
  3. var client = new OssClient(endpoint, accessKeyId, accessKeySecret);
  4. try
  5. {
  6. // 重要: 这时候作为目录的key必须以斜线(/)结尾
  7. const string key = "yourfolder/";
  8. // 此时的目录是一个内容为空的文件
  9. using (var stream = new MemoryStream())
  10. {
  11. client.PutObject(bucketName, key, stream);
  12. Console.WriteLine("Create dir {0} succeeded", key);
  13. }
  14. }
  15. catch (Exception ex)
  16. {
  17. Console.WriteLine("Create dir failed, {0}", ex.Message);
  18. }

说明:

  • 完整代码参考GitHub
  • 创建模拟文件夹本质上来说是创建了一个空文件。
  • 对于这个文件照样可以上传下载,只是控制台会对以”/“结尾的文件以文件夹的方式展示。
  • 所以用户可以使用上述方式来实现创建模拟文件夹。
  • 而对文件夹的访问可以参看文件夹功能模拟

异步上传

  1. using Aliyun.OSS;
  2. // 初始化OssClient
  3. var client = new OssClient(endpoint, accessKeyId, accessKeySecret);
  4. static AutoResetEvent _event = new AutoResetEvent(false);
  5. public static void AsyncPutObject()
  6. {
  7. try
  8. {
  9. using (var fs = File.Open(fileToUpload, FileMode.Open))
  10. {
  11. var metadata = new ObjectMetadata();
  12. metadata.CacheControl = "No-Cache";
  13. metadata.ContentType = "text/html";
  14. client.BeginPutObject(bucketName, key, fs, metadata, PutObjectCallback, new string('a', 8));
  15. _event.WaitOne();
  16. }
  17. }
  18. catch (Exception ex)
  19. {
  20. Console.WriteLine("Put object failed, {0}", ex.Message);
  21. }
  22. }
  23. private static void PutObjectCallback(IAsyncResult ar)
  24. {
  25. try
  26. {
  27. var result = client.EndPutObject(ar);
  28. Console.WriteLine("ETag:{0}", result.ETag);
  29. Console.WriteLine("User Parameter:{0}", ar.AsyncState as string);
  30. Console.WriteLine("Put object succeeded");
  31. }
  32. catch (Exception ex)
  33. {
  34. Console.WriteLine("Put object failed, {0}", ex.Message);
  35. }
  36. finally
  37. {
  38. _event.Set();
  39. }
  40. }

说明:

  • 完整代码参考GitHub
  • 使用异步上传时您需要实现自己的回调处理函数。

追加上传

追加写的方式上传文件。详细介绍请参考API

  1. using Aliyun.OSS;
  2. // 初始化OssClient
  3. var client = new OssClient(endpoint, accessKeyId, accessKeySecret);
  4. /// <summary>
  5. /// 追加内容到指定的OSS文件中。
  6. /// </summary>
  7. /// <param name="bucketName">指定的存储空间名称。</param>
  8. /// <param name="key">OSS上会被追加内容的文件名称。</param>
  9. /// <param name="fileToUpload">指定被追加的文件的路径。</param>
  10. public static void AppendObject(string bucketName, string key, string fileToUpload)
  11. {
  12. //第一次追加文件的时候,文件可能已经存在,先获取文件的当前长度,如果不存在,position为0
  13. long position = 0;
  14. try
  15. {
  16. var metadata = client.GetObjectMetadata(bucketName, key);
  17. position = metadata.ContentLength;
  18. }
  19. catch(Exception) {}
  20. try
  21. {
  22. using (var fs = File.Open(fileToUpload, FileMode.Open))
  23. {
  24. var request = new AppendObjectRequest(bucketName, key)
  25. {
  26. ObjectMetadata = new ObjectMetadata(),
  27. Content = fs,
  28. Position = position
  29. };
  30. var result = client.AppendObject(request);
  31. // 设置下次追加文件时的position位置
  32. position = result.NextAppendPosition;
  33. Console.WriteLine("Append object succeeded, next append position:{0}", position);
  34. }
  35. // 再次追加文件,这时候的position值可以从上次的结果中获取到
  36. using (var fs = File.Open(fileToUpload, FileMode.Open))
  37. {
  38. var request = new AppendObjectRequest(bucketName, key)
  39. {
  40. ObjectMetadata = new ObjectMetadata(),
  41. Content = fs,
  42. Position = position
  43. };
  44. var result = client.AppendObject(request);
  45. position = result.NextAppendPosition;
  46. Console.WriteLine("Append object succeeded, next append position:{0}", position);
  47. }
  48. }
  49. catch (Exception ex)
  50. {
  51. Console.WriteLine("Append object failed, {0}", ex.Message);
  52. }
  53. }

说明:

分片上传

除了通过PutObject接口上传文件到OSS以外,OSS还提供了另外一种上传模式 —— Multipart Upload。

用户可以在如下的应用场景内(但不仅限于此),使用Multipart Upload上传模式,如:

  • 需要支持断点上传。
  • 上传超过100MB大小的文件。
  • 网络条件较差,和OSS的服务器之间的链接经常断开。
  • 上传文件之前,无法确定上传文件的大小。

下面我们将一步步学习怎样实现Multipart Upload。

分步完成Multipart Upload

初始化Multipart Upload

我们使用 initiateMultipartUpload 方法来初始化一个分片上传事件:

  1. using Aliyun.OSS;
  2. // 初始化OssClient
  3. var client = new OssClient(endpoint, accessKeyId, accessKeySecret);
  4. try
  5. {
  6. string bucketName = "your-bucket-name";
  7. string key = "your-key";
  8. // 开始Multipart Upload
  9. var request = new InitiateMultipartUploadRequest(bucketName, key);
  10. var result = client.InitiateMultipartUpload(request);
  11. // 打印UploadId
  12. Console.WriteLine("Init multi part upload succeeded");
  13. Console.WriteLine("Upload Id:{0}", result.UploadId);
  14. }
  15. catch (Exception ex)
  16. {
  17. Console.WriteLine("Init multi part upload failed, {0}", ex.Message);
  18. }

说明:

  • 完整代码参考GitHub
  • 我们用InitiateMultipartUploadRequest来指定上传文件的名字和所属存储空间(Bucket)。
  • 在InitiateMultipartUploadRequest中,您也可以设置ObjectMeta,但是不必指定其中的ContentLength。
  • initiateMultipartUpload 的返回结果中含有UploadId ,它是区分分片上传事件的唯一标识,在后面的操作中,我们将用到它。

Upload Part本地上传

我们把本地文件分片上传。假设有一个文件,本地路径为 /path/to/file.zip 由于文件比较大,我们将其分片传输到OSS中。

  1. using Aliyun.OSS;
  2. // 初始化OssClient
  3. var client = new OssClient(endpoint, accessKeyId, accessKeySecret);
  4. // 计算片个数
  5. var fi = new FileInfo(fileToUpload);
  6. var fileSize = fi.Length;
  7. var partCount = fileSize / partSize;
  8. if (fileSize % partSize != 0)
  9. {
  10. partCount++;
  11. }
  12. // 开始分片上传
  13. try
  14. {
  15. var partETags = new List<PartETag>();
  16. using (var fs = File.Open(fileToUpload, FileMode.Open))
  17. {
  18. for (var i = 0; i < partCount; i++)
  19. {
  20. var skipBytes = (long)partSize * i;
  21. //定位到本次上传片应该开始的位置
  22. fs.Seek(skipBytes, 0);
  23. //计算本次上传的片大小,最后一片为剩余的数据大小,其余片都是part size大小。
  24. var size = (partSize < fileSize - skipBytes) ? partSize : (fileSize - skipBytes);
  25. var request = new UploadPartRequest(bucketName, objectName, uploadId)
  26. {
  27. InputStream = fs,
  28. PartSize = size,
  29. PartNumber = i + 1
  30. };
  31. //调用UploadPart接口执行上传功能,返回结果中包含了这个数据片的ETag值
  32. var result = _ossClient.UploadPart(request);
  33. partETags.Add(result.PartETag);
  34. }
  35. Console.WriteLine("Put multi part upload succeeded");
  36. }
  37. }
  38. catch (Exception ex)
  39. {
  40. Console.WriteLine("Put multi part upload failed, {0}", ex.Message);
  41. }

说明: 完整代码参考GitHub

上面程序的核心是调用UploadPart方法来上传每一个分片,但是要注意以下几点:

  • UploadPart 方法要求除最后一个Part以外,其他的Part大小都要大于100KB。但是Upload Part接口并不会立即校验上传Part的大小(因为不知道是否为最后一片);只有当Complete Multipart Upload的时候才会校验。
  • OSS会将服务器端收到Part数据的MD5值放在ETag头内返回给用户。
  • 为了保证数据在网络传输过程中不出现错误,SDK会自动设置Content-MD5,OSS会计算上传数据的MD5值与SDK计算的MD5值比较,如果不一致返回InvalidDigest错误码。
  • Part号码的范围是1~10000。如果超出这个范围,OSS将返回InvalidArgument的错误码。
  • 每次上传part时都要把流定位到此次上传片开头所对应的位置。
  • 每次上传part之后,OSS的返回结果会包含一个 PartETag 对象,他是上传片的ETag与片编号(PartNumber)的组合,在后续完成分片上传的步骤中会用到它,因此我们需要将其保存起来。一般来讲我们将这些 PartETag 对象保存到List中。

完成分片上传

完成分片上传代码如下:

  1. using Aliyun.OSS;
  2. // 初始化OssClient
  3. var client = new OssClient(endpoint, accessKeyId, accessKeySecret);
  4. try
  5. {
  6. var completeMultipartUploadRequest = new CompleteMultipartUploadRequest(bucketName, key, uploadId);
  7. foreach (var partETag in partETags)
  8. {
  9. completeMultipartUploadRequest.PartETags.Add(partETag);
  10. }
  11. var result = client.CompleteMultipartUpload(completeMultipartUploadRequest);
  12. Console.WriteLine("complete multi part succeeded");
  13. }
  14. catch (Exception ex)
  15. {
  16. Console.WriteLine("complete multi part failed, {0}", ex.Message);
  17. }

说明:

  • 完整代码参考GitHub
  • 上面代码中的 partETags 就是进行分片上传中保存的partETag的列表,OSS收到用户提交的Part列表后,会逐一验证每个数据Part的有效性。
  • 当所有的数据Part验证通过后,OSS会将这些part组合成一个完整的文件。

取消分片上传事件

我们可以用 AbortMultipartUpload 方法取消分片上传。

  1. using Aliyun.OSS;
  2. // 初始化OssClient
  3. var client = new OssClient(endpoint, accessKeyId, accessKeySecret);
  4. try
  5. {
  6. var request = new AbortMultipartUpload(bucketName, key, uploadId);
  7. client.AbortMultipartUpload(request);
  8. Console.WriteLine("Abort multi part succeeded");
  9. }
  10. catch (Exception ex)
  11. {
  12. Console.WriteLine("Abort multi part failed, {0}", ex.Message);
  13. }

说明: 完整代码参考请GitHub

获取存储空间内所有分片上传事件

我们可以用 ListMultipartUploads 方法获取存储空间内所有上传事件。

  1. using Aliyun.OSS;
  2. // 初始化OssClient
  3. var client = new OssClient(endpoint, accessKeyId, accessKeySecret);
  4. try
  5. {
  6. // 获取Bucket内所有上传事件
  7. var request = new ListMultipartUploadsRequest(bucketName);
  8. var multipartUploadListing = client.ListMultipartUploads(request);
  9. Console.WriteLine("List multi part succeeded");
  10. // 获取各事件信息
  11. var multipartUploads = multipartUploadListing.MultipartUploads;
  12. foreach (var mu : multipartUploads)
  13. {
  14. Console.WriteLine("Key:{0}, UploadId:{1}", mu.Key , mu.UploadId);
  15. }
  16. var commonPrefixes = multipartUploadListing.CommonPrefixes;
  17. foreach (var prefix : commonPrefixes)
  18. {
  19. Console.WriteLine("Prefix:{0}", prefix);
  20. }
  21. }
  22. catch (Exception ex)
  23. {
  24. Console.WriteLine("List multi part uploads failed, {0}", ex.Message);
  25. }

说明:

  • 完整代码请参考GitHub
  • 默认情况下,如果存储空间中的分片上传事件的数量大于1000,则只会返回1000个文件, 且返回结果中 IsTruncated 为 false,返回 NextKeyMarker 和 NextUploadIdMarker 作为下此读取的起点。
  • 若想增大返回分片上传事件数目,可以修改 MaxUploads 参数,或者使用 KeyMarker 以及 UploadIdMarker 参数分次读取。

获取所有已上传的片信息

我们可以用 ListParts 方法获取某个上传事件所有已上传的片。

  1. using Aliyun.OSS;
  2. // 初始化OssClient
  3. var client = new OssClient(endpoint, accessKeyId, accessKeySecret);
  4. try
  5. {
  6. var listPartsRequest = new ListPartsRequest(bucketName, key, uploadId);
  7. var listPartsResult = client.ListParts(listPartsRequest);
  8. Console.WriteLine("List parts succeeded");
  9. // 遍历所有Part
  10. var parts = listPartsResult.Parts;
  11. foreach (var part : parts)
  12. {
  13. Console.WriteLine("partNumber:{0}, ETag:{1}, Size:{2}", part.PartNumber, part.ETag, part.Size);
  14. }
  15. }
  16. catch (Exception ex)
  17. {
  18. Console.WriteLine("List parts failed, {0}", ex.Message);
  19. }

说明:

  • 完整代码参考GitHub
  • 默认情况下,如果存储空间中的分片上传事件的数量大于1000,则只会返回1000个Multipart Upload信息,且返回结果中 IsTruncated 为false,并返回 NextPartNumberMarker作为下此读取的起点。
  • 若想增大返回分片上传事件数目,可以修改 MaxParts 参数,或者使用 PartNumberMarker 参数分次读取。

通过断点续传上传

除了支持分片上传外,还提供了断点续传功能,如果某次上传中断,下次可以从上次失败的位置开始上传,以便加快速度。

  1. using Aliyun.OSS;
  2. // 初始化OssClient
  3. var client = new OssClient(endpoint, accessKeyId, accessKeySecret);
  4. /// <summary>
  5. /// 断点续传
  6. /// </summary>
  7. /// <param name="bucketName">指定的存储空间名称。</param>
  8. /// <param name="key">保存到OSS上的名称。</param>
  9. /// <param name="fileToUpload">指定上传文件的路径。</param>
  10. /// <param name="checkpointDir">保存断点续传中间状态文件的目录,如果指定了,则会具有断点续传功能,否则会重新上传</param>
  11. public static void ResumableUploadObject(string bucketName, string key, string fileToUpload, string checkpointDir)
  12. {
  13. try
  14. {
  15. client.ResumableUploadObject(bucketName, key, fileToUpload, null, checkpointDir);
  16. Console.WriteLine("Resumable upload object:{0} succeeded", key);
  17. }
  18. catch (Exception ex)
  19. {
  20. Console.WriteLine("Resumable upload object failed, {0}", ex.Message);
  21. }
  22. }

说明:

  • 完整代码参考GitHub
  • checkpointDir目录中会保存断点续传的中间状态,用于失败后,下次继续上传时使用。
  • 如果checkpointDir为null,断点续传功能不会生效,每次都会重新上传。

上传回调

OSS在上传文件完成的时,可以提供回调(Callback)给应用服务器。用户只需要在发送给OSS的请求中携带相应的回调参数,即能实现回调。支持上传回调接口有:PutObject、PostObject、CompleteMultipartUpload。您想了解上传回调的更详细信息,请参考上传回调。下面以 PutObject 为例说明上传回调的用法。

  1. using Aliyun.OSS;
  2. var client = new OssClient(endpoint, accessKeyId, accessKeySecret);
  3. public static void PutObjectCallback(string bucketName)
  4. {
  5. const string key = "PutObjectProgress";
  6. const string callbackUrl = "http://callback.oss-demo.com:23450";
  7. const string callbackBody = "bucket=${bucket}&object=${object}&etag=${etag}&size=${size}&mimeType=${mimeType}&" +
  8. "my_var1=${x:var1}&my_var2=${x:var2}";
  9. try
  10. {
  11. string responseContent = "";
  12. var metadata = BuildCallbackMetadata(callbackUrl, callbackBody);
  13. using (var fs = File.Open(fileToUpload, FileMode.Open))
  14. {
  15. var putObjectRequest = new PutObjectRequest(bucketName, key, fs, metadata);
  16. var result = client.PutObject(putObjectRequest);
  17. responseContent = GetCallbackResponse(result);
  18. }
  19. Console.WriteLine("Put object:{0} succeeded, callback response content:{1}", key, responseContent);
  20. }
  21. catch (OssException ex)
  22. {
  23. Console.WriteLine("Failed with error code: {0}; Error info: {1}. \nRequestID:{2}\tHostID:{3}",
  24. ex.ErrorCode, ex.Message, ex.RequestId, ex.HostId);
  25. }
  26. catch (Exception ex)
  27. {
  28. Console.WriteLine("Failed with error info: {0}", ex.Message);
  29. }
  30. }
  31. private static string GetCallbackResponse(PutObjectResult putObjectResult)
  32. {
  33. string callbackResponse = null;
  34. using (var stream = putObjectResult.ResponseStream)
  35. {
  36. var buffer = new byte[4 * 1024];
  37. var bytesRead = stream.Read(buffer, 0, buffer.Length);
  38. callbackResponse = Encoding.Default.GetString(buffer, 0, bytesRead);
  39. }
  40. return callbackResponse;
  41. }

说明:

  • PutObject、CompleteMultipartUpload、ResumableUploadObject支持上传回调,完整代码请参考GitHub
  • 上传回调的调试方法和错误排除,请参考上传回调错误及排除

进度条

OSS .Net SDK支持进度条功能,指示上传、下载的进度。下面的代码以 PutObject 为例,说明进度条功能的使用方法。

  1. using Aliyun.OSS;
  2. var client = new OssClient(endpoint, accessKeyId, accessKeySecret);
  3. public static void PutObjectProgress(string bucketName)
  4. {
  5. const string key = "PutObjectProgress";
  6. try
  7. {
  8. using (var fs = File.Open(fileToUpload, FileMode.Open))
  9. {
  10. var putObjectRequest = new PutObjectRequest(bucketName, key, fs);
  11. putObjectRequest.StreamTransferProgress += streamProgressCallback;
  12. client.PutObject(putObjectRequest);
  13. }
  14. Console.WriteLine("Put object:{0} succeeded", key);
  15. }
  16. catch (OssException ex)
  17. {
  18. Console.WriteLine("Failed with error code: {0}; Error info: {1}. \nRequestID:{2}\tHostID:{3}",
  19. ex.ErrorCode, ex.Message, ex.RequestId, ex.HostId);
  20. }
  21. catch (Exception ex)
  22. {
  23. Console.WriteLine("Failed with error info: {0}", ex.Message);
  24. }
  25. }
  26. private static void streamProgressCallback(object sender, StreamTransferProgressArgs args)
  27. {
  28. System.Console.WriteLine("ProgressCallback - TotalBytes:{0}, TransferredBytes:{1}",
  29. args.TotalBytes, args.TransferredBytes);
  30. }

说明: PutObject、AppendObject、UploadPart、ResumableUploadObject、GetObject都支持进度功能,完整代码请参考GitHub

本文导读目录