AWS Developer Blog

Working with Amazon S3 Object Versions and the AWS SDK for .NET

by Norm Johanson | on | in .NET | Permalink | Comments |  Share

Amazon S3 allows you to enable versioning for a bucket. You can enable or disable versioning with the SDK by calling the PutBucketVersioning method. Note, all code samples were written for our new version 2 of the SDK. Users of version 1 of the SDK will notice some slight name changes.

s3Client.PutBucketVersioning(new PutBucketVersioningRequest
{
    BucketName = versionBucket,
    VersioningConfig = new S3BucketVersioningConfig() { Status = VersionStatus.Enabled }
});

Once versioning is enabled, every PutObject call with the same key will add a new version of the object with a different version ID instead of overwriting the object. For example, running the code below will create three versions of the "sample.txt" object. The sleeps are added to give a more obvious difference in the timestamps.

var putRequest = new PutObjectRequest
{
    BucketName = versionBucket,
    Key = "sample.txt",
    ContentBody = "Content For Version 1"
};

s3Client.PutObject(putRequest);

Thread.Sleep(TimeSpan.FromSeconds(10));

s3Client.PutObject(new PutObjectRequest
{
    BucketName = versionBucket,
    Key = "sample.txt",
    ContentBody = "Content For Version 2"
});

Thread.Sleep(TimeSpan.FromSeconds(10));

s3Client.PutObject(new PutObjectRequest
{
    BucketName = versionBucket,
    Key = "sample.txt",
    ContentBody = "Content For Version 3"
});

Now, if you call the GetObject method without specifying a version ID like this:

var getRequest = new GetObjectRequest
{
    BucketName = versionBucket,
    Key = "sample.txt"
};

using (GetObjectResponse getResponse = s3Client.GetObject(getRequest))
using (StreamReader reader = new StreamReader(getResponse.ResponseStream))
{
    Console.WriteLine(reader.ReadToEnd());
}

// Outputs:
Content For Version 3

It will print out the contents of the last object that was put into the bucket.

Use the ListVersions method to get the list of versions.

var listResponse = s3Client.ListVersions(new ListVersionsRequest
{
    BucketName = versionBucket,
    Prefix = "sample.txt"                    
});

foreach(var version in listResponse.Versions)
{
    Console.WriteLine("Key: {0}, Version ID: {1}, IsLatest: {2}, Modified: {3}", 
        version.Key, version.VersionId, version.IsLatest, version.LastModified);
}

// Output:
Key: sample.txt, Version ID: nx5sVCpUSdpHzPBpOICF.eELc2nUsm3c, IsLatest: True, Modified: 10/29/2013 4:45:07 PM
Key: sample.txt, Version ID: LOgcIIrvtM0ZqYfkvfRz3UMdgdmRXNWE, IsLatest: False, Modified: 10/29/2013 4:44:56 PM
Key: sample.txt, Version ID: XxnZRKXHZ7cHYiogeCHXXxccojj9DLK5, IsLatest: False, Modified: 10/29/2013 4:44:46 PM

To get a specific version of an object, you simply need to specify the VersionId property when performing a GetObject.

var earliestVersion = listResponse.Versions.OrderBy(x => x.LastModified).First();

var getRequest = new GetObjectRequest
{
    BucketName = versionBucket,
    Key = "sample.txt",
    VersionId = earliestVersion.VersionId
};

using(GetObjectResponse getResponse = s3Client.GetObject(getRequest))
using(StreamReader reader = new StreamReader(getResponse.ResponseStream))
{
    Console.WriteLine(reader.ReadToEnd());
}

// Outputs:
Content For Version 1

Deleting an object that is versioned works differently than the non-versioned objects. If you call delete like this:

s3Client.DeleteObject(new DeleteObjectRequest
{
    BucketName = versionBucket,
    Key = "sample.txt"
});

and then try to do a GetObject for the "sample.txt" object, S3 will return an error that the object doesn’t exist. What S3 actually does when you call delete for a versioned object is insert a delete marker. You can see this if you list the versions again.

var  listResponse = s3Client.ListVersions(new ListVersionsRequest
{
    BucketName = versionBucket,
    Prefix = "sample.txt"                    
});

foreach (var version in listResponse.Versions)
{
    Console.WriteLine("Key: {0}, Version ID: {1}, IsLatest: {2}, IsDeleteMarker: {3}", 
        version.Key, version.VersionId, version.IsLatest, version.IsDeleteMarker);
}

// Outputs:
Key: sample.txt, Version ID: YRsryuUODxDujL4Y4iJjRLKweHrV0t2U, IsLatest: True, IsDeleteMarker: True
Key: sample.txt, Version ID: nx5sVCpUSdpHzPBpOICF.eELc2nUsm3c, IsLatest: False, IsDeleteMarker: False
Key: sample.txt, Version ID: LOgcIIrvtM0ZqYfkvfRz3UMdgdmRXNWE, IsLatest: False, IsDeleteMarker: False
Key: sample.txt, Version ID: XxnZRKXHZ7cHYiogeCHXXxccojj9DLK5, IsLatest: False, IsDeleteMarker: False

If you want to delete a specific version of an object, when calling DeleteObject, set the VersionId property. This is also how you can restore an object by deleting the delete marker.

var deleteMarkerVersion = listResponse.Versions.FirstOrDefault(x => x.IsDeleteMarker && x.IsLatest);
if (deleteMarkerVersion != null)
{
    s3Client.DeleteObject(new DeleteObjectRequest
    {
        BucketName = versionBucket,
        Key = "sample.txt",
        VersionId = deleteMarkerVersion.VersionId
    });
}

Now, calls to GetObject for the "sample.txt" object will succeed again.