表单上传(C# SDK V2)

OSS表单上传允许网页应用通过标准HTML表单直接将文件上传至OSS。本文介绍如何使用C# SDK V2生成Post签名和Post Policy等信息,并调用HTTP Post方法上传文件到OSS。

注意事项

  • 本文示例代码以华东1(杭州)的地域IDcn-hangzhou为例,默认使用外网Endpoint,如果您希望通过与OSS同地域的其他阿里云产品访问OSS,请使用内网Endpoint。关于OSS支持的RegionEndpoint的对应关系,请参见OSS地域和访问域名

  • 通过表单上传的方式上传的Object大小不能超过5 GB。

示例代码

以下代码示例实现了表单上传的完整过程,主要步骤如下:

  1. 初始化配置与参数:设置区域(region)、存储桶(bucket)、对象键(key)和产品(product),并从环境变量中加载访问凭证和准备上传的内容content。

  2. 创建Post Policy:定义上传请求的有效时间和条件。

  3. 序列化并编码Policy:将Policy序列化为JSON字符串并进行Base64编码。

  4. 生成签名密钥:使用HMAC-SHA256算法,基于AccessKeySecret生成初始密钥,再依次对日期、区域、产品和请求类型进行哈希计算,生成最终的签名密钥。

  5. 构建请求体:创建符合HTTP POST规范的Multipart表单数据,添加对象键、Base64编码的Policy、签名版本、凭证信息、请求日期、签名和要上传的数据。

  6. 创建并执行请求:创建HTTP客户端并发送POST请求到OSS服务端点,处理响应结果,输出状态码和响应头信息。

using OSS = AlibabaCloud.OSS.V2;  // 为阿里云OSS SDK创建别名,简化后续使用
using System.Globalization;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;

var region = "cn-hangzhou";  // 必须项,设置Bucket所在的区域(Region)。以华东1(杭州)为例,Region填写为cn-hangzhou
var bucket = "you bucket name";  // 必须项,Bucket名称
var key = "your object key";  // 必须项,上传的目标对象名称
var product = "oss";  // 必须项,OSS产品标识

// 将字节数组转换为十六进制字符串
static string ToHexString(byte[] data, bool lowercase)
{
    var sb = new StringBuilder();
    for (var i = 0; i < data.Length; i++) sb.Append(data[i].ToString(lowercase ? "x2" : "X2"));
    return sb.ToString();
}

// 给字符串添加双引号
static string quote(string value)
{
    return $"\"{value}\"";
}

// 加载OSS SDK的默认配置,此配置会自动从环境变量中读取凭证信息(如AccessKey)
var cfg = OSS.Configuration.LoadDefault();
// 显式设置使用环境变量获取凭证,用于身份验证(格式:OSS_ACCESS_KEY_ID、OSS_ACCESS_KEY_SECRET)
var credentialsProvider = new OSS.Credentials.EnvironmentVariableCredentialsProvider();
var credentials = credentialsProvider.GetCredentials();

// 要上传的内容
var content = "hello oss";

// 构建Policy(上传策略)
var utcTime = DateTime.UtcNow;
var date = utcTime.ToUniversalTime().ToString("yyyyMMdd", CultureInfo.InvariantCulture);
var dateTime = utcTime.ToUniversalTime().ToString("yyyyMMdd'T'HHmmss'Z'", CultureInfo.InvariantCulture);
var expiration = utcTime.AddHours(1);// 签名有效期1小时

// 构建凭证信息
var credentialInfo = $"{credentials.AccessKeyId}/{date}/{region}/{product}/aliyun_v4_request";

// 构建Policy JSON
var policyMap = new Dictionary<string, Object>()
{
    // 设置策略过期时间
    { "expiration",
        expiration.ToUniversalTime().ToString("yyyy-MM-dd'T'HH:mm:ss.000'Z'", CultureInfo.InvariantCulture)
    },
    // 设置上传条件
    { "conditions", new Object[]{
            // 必须上传到指定的Bucket
            new Dictionary<string, string>() {{ "bucket", bucket } },
            // 指定签名版本
            new Dictionary<string, string>() {{ "x-oss-signature-version", "OSS4-HMAC-SHA256" } },
            // 指定凭证信息
            new Dictionary<string, string>() {{ "x-oss-credential", credentialInfo } },
            // 指定请求时间
            new Dictionary<string, string>() {{ "x-oss-date", dateTime } },
            // 限制上传内容长度范围
            new Object[]{"content-length-range", 1, 1024 },
            //new Object[]{"eq", "$success_action_status", "201"},
            //new Object[]{"starts-with", "$key", "user/eric/"},
            //new Object[]{"in", "$content-type", new string[]{"image/jpg", "image/png"}},
            //new Object[]{ "not-in", "$cache-control", new string[]{ "no-cache" } },
        }
    },
};

// 序列化为JSON字符串
var policy = JsonSerializer.Serialize(policyMap);

// 对Policy进行Base64编码
var stringToSign = Convert.ToBase64String(Encoding.UTF8.GetBytes(policy));

// 生成签名密钥
using var kha = new HMACSHA256();

// 初始密钥由AccessKeySecret生成
var ksecret = Encoding.UTF8.GetBytes("aliyun_v4" + credentials.AccessKeySecret);

// 逐级生成签名密钥
kha.Key = ksecret;
var hashDate = kha.ComputeHash(Encoding.UTF8.GetBytes(date));
kha.Key = hashDate;
var hashRegion = kha.ComputeHash(Encoding.UTF8.GetBytes(region));
kha.Key = hashRegion;
var hashProduct = kha.ComputeHash(Encoding.UTF8.GetBytes(product));
kha.Key = hashProduct;
var signingKey = kha.ComputeHash(Encoding.UTF8.GetBytes("aliyun_v4_request"));

// 计算最终签名
kha.Key = signingKey;
var signature = ToHexString(kha.ComputeHash(Encoding.UTF8.GetBytes(stringToSign)), true);

// 构建Multipart表单数据
using var formData = new MultipartFormDataContent();
// 处理boundary引号问题
var boundary = formData.Headers.ContentType!.Parameters.ElementAt(0).Value!;
formData.Headers.ContentType.Parameters.ElementAt(0).Value = boundary.Trim('"');

// 添加表单字段 - 对象键名
formData.Add(new ByteArrayContent(Encoding.UTF8.GetBytes(key)), quote("key"));

// 可以添加元数据
//formData.Add(new ByteArrayContent(Encoding.UTF8.GetBytes(value)), quote("x-oss-"));

// 添加Policy
formData.Add(new ByteArrayContent(Encoding.UTF8.GetBytes(stringToSign)), quote("policy"));

// 添加签名相关信息
formData.Add(new ByteArrayContent(Encoding.UTF8.GetBytes("OSS4-HMAC-SHA256")), quote("x-oss-signature-version"));
formData.Add(new ByteArrayContent(Encoding.UTF8.GetBytes(credentialInfo)), quote("x-oss-credential"));
formData.Add(new ByteArrayContent(Encoding.UTF8.GetBytes(dateTime)), quote("x-oss-date"));
formData.Add(new ByteArrayContent(Encoding.UTF8.GetBytes(signature)), quote("x-oss-signature"));

// 添加要上传的内容
formData.Add(new ByteArrayContent(Encoding.UTF8.GetBytes(content)), quote("file"));

// 发送POST请求到OSS
using var hc = new HttpClient();
var result = await hc.PostAsync($"http://{bucket}.oss-{region}.aliyuncs.com/", formData);

// 打印上传结果
Console.WriteLine("PostObject done");  // 提示操作完成
Console.WriteLine($"StatusCode: {result.StatusCode}");  // HTTP状态码
Console.WriteLine("Response Headers:");  // 响应头信息
result.Headers.ToList().ForEach(x => Console.WriteLine(x.Key + " : " + String.Join(",", x.Value.ToList())));  // 遍历并打印所有响应头

相关文档

关于表单上传的完整示例,请参见postObject.cs