This guide explains how to upload bulletins using the Multipart API in a structured way. It involves three main steps:
Initialize the upload using the API
Upload each part and keep track of the parts
Call the
CompleteAPI and pass the part IDs
Step 1: Initialize the Multipart Upload
The first step is to initialize the upload process by calling the InitializeMultipartUpload endpoint.
Endpoint:
POST /v1/bulletins/multipart
Request Parameters:
tt: Represents the type of content being uploaded. For bulletins, use the valuebulletins.Body:
{ "Name": "BulletinFile.png", "Parts": 5, "ZoneID": 1 }
Expected Response:
{
"UploadId": "unique-upload-id",
"Key": "Temp/unique-media-id/BulletinFile.png",
"Name": "BulletinFile.png",
"Urls": [
"signed-url-part-1",
"signed-url-part-2",
"signed-url-part-3",
"signed-url-part-4",
"signed-url-part-5"
]
}
Explanation:
The
UploadIdis a unique identifier for this upload session.The
Urlsarray contains pre-signed URLs for each part of the file. These URLs allow you to upload the file parts directly
Step 2: Upload Each Part
Once the upload is initialized, you need to upload each part of the file to the corresponding signed URL.
Procedure:
Split your file into parts (if necessary) according to the
Partsvalue specified in the initialization request.Use an HTTP client (like
cURL, Postman, or any programming language) to upload each part to the respective signed URL.
Example Using cURL:
curl -X PUT "signed-url-part-1" \
-T "part1-of-file"
curl -X PUT "signed-url-part-2" \
-T "part2-of-file"
# Repeat for all parts
Important Notes:
Ensure that parts are uploaded in the correct order.
After each upload, the response from the server will contain an
ETag. Save theseETagsas they will be needed in the final step.
Step 3: Complete the Multipart Upload
Once all parts have been uploaded, call the CompleteMultipartUpload endpoint to finalize the process and register the bulletin in the database.
Endpoint:
POST /v1/bulletins/multipart/complete
Request Parameters:
Body:
{ "UploadId": "unique-upload-id", "Key": "Temp/unique-media-id/BulletinFile.png", "ETags": [ "etag-for-part-1", "etag-for-part-2", "etag-for-part-3", "etag-for-part-4", "etag-for-part-5" ], "ZoneID": 1, "Name": "BulletinFile.png" }
Expected Response:
{
"Id": "bulletin-id",
"Name": "BulletinFile.png",
"Status": "Uploaded",
"ZoneID": 1,
"Url": "final-url-to-access-the-file"
}
Demonstration with C# Console App
To test this API locally, you can use the following C# console application. This app demonstrates the entire process: initializing the upload, uploading parts, and completing the multipart upload.
C# Code Example
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
class Program
{
private static readonly string ApiBaseUrl = "http://localhost/CarouselAPI/v1";
private static readonly string AuthToken = "<your-token-here>"; // Replace with a valid token.
private static readonly int zoneId = 43; // Replace with a valid zone id.
static async Task Main()
{
// Generate test files for upload
GenerateTestFiles(3);
// Step 1: Initialize Multipart Upload
Console.WriteLine("Initializing multipart upload...");
var initializeResponse = await InitializeMultipartUploadAsync("BulletinFile.png", 3);
if (initializeResponse == null)
{
Console.WriteLine("Failed to initialize upload.");
return;
}
Console.WriteLine($"UploadId: {initializeResponse.UploadId}");
Console.WriteLine("Uploading parts...");
// Step 2: Upload Each Part
var etags = new List<string>();
for (int i = 0; i < initializeResponse.Urls.Count; i++)
{
var partUrl = initializeResponse.Urls[i];
var filePath = $"part{i + 1}.bin"; // Replace with actual file paths for testing.
var etag = await UploadPartAsync(partUrl, filePath);
if (!string.IsNullOrEmpty(etag))
{
etags.Add(etag);
Console.WriteLine($"Uploaded part {i + 1}: ETag = {etag}");
}
else
{
Console.WriteLine($"Failed to upload part {i + 1}.");
return;
}
}
// Step 3: Complete Multipart Upload
Console.WriteLine("Completing multipart upload...");
var completeResult = await CompleteMultipartUploadAsync(
initializeResponse.UploadId,
initializeResponse.Key,
initializeResponse.Name,
zoneId,
etags
);
if (completeResult)
Console.WriteLine("Multipart upload completed successfully.");
else
Console.WriteLine("Failed to complete upload.");
}
private static async Task<MultipartUploadInitializeResponse?> InitializeMultipartUploadAsync(string fileName, int parts)
{
using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", AuthToken);
var requestContent = new StringContent(JsonSerializer.Serialize(new
{
Name = fileName,
Parts = parts,
ZoneID = zoneId
}), Encoding.UTF8, "application/json");
var response = await client.PostAsync($"{ApiBaseUrl}/bulletins/multipart", requestContent);
if (response.IsSuccessStatusCode)
{
var content = await response.Content.ReadAsStringAsync();
return JsonSerializer.Deserialize<MultipartUploadInitializeResponse>(content);
}
Console.WriteLine($"Failed to initialize upload. Status Code: {response.StatusCode}");
return null;
}
private static async Task<string?> UploadPartAsync(string url, string filePath)
{
using var client = new HttpClient();
using var fileStream = File.OpenRead(filePath);
var content = new StreamContent(fileStream);
content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
var response = await client.PutAsync(url, content);
if (response.IsSuccessStatusCode && response.Headers.ETag != null)
{
return response.Headers.ETag.Tag; // Return ETag from the response.
}
Console.WriteLine($"Failed to upload part. Status Code: {response.StatusCode}");
return null;
}
private static async Task<bool> CompleteMultipartUploadAsync(string uploadId, string key, string name, int zoneId, List<string> etags)
{
using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", AuthToken);
var requestContent = new StringContent(JsonSerializer.Serialize(new
{
UploadId = uploadId,
Key = key,
Name = name,
ZoneID = zoneId,
ETags = etags
}), Encoding.UTF8, "application/json");
var response = await client.PostAsync($"{ApiBaseUrl}/bulletins/multipart/complete", requestContent);
if (response.IsSuccessStatusCode)
{
Console.WriteLine("Upload completed successfully.");
return true;
}
Console.WriteLine($"Failed to complete upload. Status Code: {response.StatusCode}");
return false;
}
private static void GenerateTestFiles(int partCount)
{
for (int i = 1; i <= partCount; i++)
{
var filePath = $"part{i}.png";
using var fileStream = new FileStream(filePath, FileMode.Create, FileAccess.Write);
var randomData = new byte[5 * 1024 * 1024]; // 5 MB of random data
new Random().NextBytes(randomData);
fileStream.Write(randomData, 0, randomData.Length);
}
Console.WriteLine($"Generated {partCount} test files.");
}
}
public class MultipartUploadInitializeResponse
{
public string UploadId { get; init; } = default!;
public string Key { get; init; } = default!;
public string Name { get; init; } = default!;
public List<string> Urls { get; init; } = default!;
}