Form upload lets web applications upload files directly to OSS using standard HTML forms. Use the OSS SDK for C# V2 to generate Post signatures and Post Policies, then call HTTP POST to upload an object to OSS.
Usage notes
The sample code uses the China (Hangzhou) region (
cn-hangzhou) and the public endpoint by default. To access OSS from other Alibaba Cloud products in the same region, use the internal endpoint instead. For a full list of regions and endpoints, see OSS regions and endpoints.The maximum object size for form upload is 5 GB.
How it works
A form upload request goes through these steps on the server side before the browser sends anything to OSS:
Initialize — Set the region, bucket, object key, and product. Load access credentials from environment variables.
Build the Post Policy — Define the expiration time and upload conditions (allowed bucket, content length range, etc.).
Encode the Policy — Serialize the Policy to JSON, then Base64-encode it to produce the string to sign.
Generate the signing key — Derive the key using HMAC-SHA256 in four rounds: seed the key with the AccessKeySecret, then hash the date, region, product, and request type in sequence.
Assemble the form fields — Construct a
multipart/form-databody containing the object key, encoded Policy, signature version, credential string, request date, and signature. Append the file content last.Send the POST request — Submit the form data to the OSS endpoint and check the response.
Sample code
The following sample shows the complete form upload flow.
The file field must be the last field in the multipart form body. OSS validates all preceding fields before reading the file content. Placing the file field earlier in the form may cause the request to fail.
using OSS = AlibabaCloud.OSS.V2; // Alias for the Alibaba Cloud OSS SDK V2.
using System.Globalization;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
// ── Configuration ──────────────────────────────────────────────────────────────
var region = "cn-hangzhou"; // Region where the bucket is located.
var bucket = "your bucket name"; // Target bucket name.
var key = "your object key"; // Destination object key.
var product = "oss"; // Product identifier. Do not change this value.
// Helper: convert a byte array to a lowercase or uppercase hex string.
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();
}
// Helper: wrap a string in double quotes (required by the multipart field name format).
static string quote(string value) => $"\"{value}\"";
// ── Credentials ────────────────────────────────────────────────────────────────
// Load SDK defaults. Credentials are read from the OSS_ACCESS_KEY_ID and
// OSS_ACCESS_KEY_SECRET environment variables.
var cfg = OSS.Configuration.LoadDefault();
var credentialsProvider = new OSS.Credentials.EnvironmentVariableCredentialsProvider();
var credentials = credentialsProvider.GetCredentials();
// ── Timestamps and credential string ──────────────────────────────────────────
var utcTime = DateTime.UtcNow;
var date = utcTime.ToString("yyyyMMdd", CultureInfo.InvariantCulture);
var dateTime = utcTime.ToString("yyyyMMdd'T'HHmmss'Z'", CultureInfo.InvariantCulture);
var expiration = utcTime.AddHours(1); // Policy expires in 1 hour.
// Credential string format: {AccessKeyId}/{date}/{region}/{product}/aliyun_v4_request
var credentialInfo = $"{credentials.AccessKeyId}/{date}/{region}/{product}/aliyun_v4_request";
// ── Post Policy ────────────────────────────────────────────────────────────────
// The Policy restricts what the browser is allowed to upload.
// All conditions listed here must be satisfied for OSS to accept the request.
var policyMap = new Dictionary<string, Object>()
{
{
"expiration",
expiration.ToString("yyyy-MM-dd'T'HH:mm:ss.000'Z'", CultureInfo.InvariantCulture)
},
{
"conditions", new Object[]
{
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 }, // Accepted size: 1–1024 bytes.
// Additional condition examples (uncomment to use):
//new Object[] { "eq", "$success_action_status", "201" }, // Exact match.
//new Object[] { "starts-with", "$key", "user/eric/" }, // Prefix match.
//new Object[] { "in", "$content-type", new string[] { "image/jpg", "image/png" } }, // Allowlist.
//new Object[] { "not-in", "$cache-control", new string[] { "no-cache" } }, // Denylist.
}
},
};
// ── Serialize and encode ───────────────────────────────────────────────────────
var policy = JsonSerializer.Serialize(policyMap);
var stringToSign = Convert.ToBase64String(Encoding.UTF8.GetBytes(policy));
// ── Signing key derivation ─────────────────────────────────────────────────────
// The signing key is derived in four HMAC-SHA256 rounds:
// HMAC("aliyun_v4" + AccessKeySecret) → hash(date) → hash(region) → hash(product) → hash("aliyun_v4_request")
using var kha = new HMACSHA256();
kha.Key = Encoding.UTF8.GetBytes("aliyun_v4" + credentials.AccessKeySecret);
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"));
// Compute the final signature over the Base64-encoded Policy.
kha.Key = signingKey;
var signature = ToHexString(kha.ComputeHash(Encoding.UTF8.GetBytes(stringToSign)), true);
// ── Build the multipart form body ──────────────────────────────────────────────
var content = "hello oss"; // Content to upload.
using var formData = new MultipartFormDataContent();
// Strip the quotes that .NET adds around the boundary value.
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(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"));
// Add object metadata if needed:
//formData.Add(new ByteArrayContent(Encoding.UTF8.GetBytes(value)), quote("x-oss-meta-<key>"));
// The file field must be last.
formData.Add(new ByteArrayContent(Encoding.UTF8.GetBytes(content)), quote("file"));
// ── Send the request ───────────────────────────────────────────────────────────
using var hc = new HttpClient();
var result = await hc.PostAsync($"http://{bucket}.oss-{region}.aliyuncs.com/", formData);
Console.WriteLine("PostObject done");
Console.WriteLine($"Status code: {result.StatusCode}");
Console.WriteLine("Response headers:");
result.Headers.ToList().ForEach(x => Console.WriteLine($"{x.Key}: {string.Join(", ", x.Value)}"));Policy conditions reference
The conditions array in a Post Policy supports five matching types:
| Type | Syntax | Example |
|---|---|---|
| Exact match | {"field": "value"} or ["eq", "$field", "value"] | {"bucket": "my-bucket"} |
| Prefix match | ["starts-with", "$field", "prefix"] | ["starts-with", "$key", "user/"] |
| Content length range | ["content-length-range", min, max] | ["content-length-range", 1, 5242880] |
| Allowlist | ["in", "$field", ["val1", "val2"]] | ["in", "$content-type", ["image/jpg", "image/png"]] |
| Denylist | ["not-in", "$field", ["val1"]] | ["not-in", "$cache-control", ["no-cache"]] |
All field names prefixed with $ refer to the corresponding form field value at request time.
Form fields reference
All fields except file are evaluated against the Policy before OSS stores the object.
| Field | Required | Description |
|---|---|---|
key | Yes | Object key (destination path in the bucket) |
policy | Yes | Base64-encoded Post Policy |
x-oss-signature-version | Yes | Signature algorithm. Set to OSS4-HMAC-SHA256. |
x-oss-credential | Yes | Credential string: {AccessKeyId}/{date}/{region}/{product}/aliyun_v4_request |
x-oss-date | Yes | Request timestamp in yyyyMMddTHHmmssZ format |
x-oss-signature | Yes | HMAC-SHA256 signature over the Base64-encoded Policy |
x-oss-meta-<key> | No | Custom object metadata |
file | Yes | File content. Must be the last field in the form. |
References
For the complete sample code, see postObject.cs.