AWS Developer Tools Blog

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

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 }
});
C#

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"
});
C#

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
C#

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
C#

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
C#

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"
});
C#

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
C#

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
    });
}
C#

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

TAGS:
Norm Johanson

Norm Johanson

Norm Johanson has been a software developer for more than 20 years developing all types of applications. Since 2010 he has been working for AWS focusing on the .NET developer experience at AWS. His experience goes back to .NET Framework 1.0 and has been his main development platform since. These days Norm is focused on combining the power of AWS and .NET Core to help .NET developers modernize their applications. You can find Norm on Twitter at @socketnorm