Skip to main content

One post tagged with ".NET"

View All Tags

· 7 min read
Iain Sutherland

If you’re contemplating the option of adapting existing .NET code that uses Amazon S3 to instead use IBC S6 for storage, one question that might be at the forefront of your mind is, well just how difficult will that be?

Speaking as a fairly experienced .NET developer I would reply, actually very easy. And I’ll demonstrate.

Prerequisites

To get started, you'll need appropriate credentials. For this tutorial, I have both AWS SDK credentials and Ionburst Cloud credentials set up on my PC. If you are already familiar with AWS credentials then you may already have a .aws/credentials credentials file in your home directory. For this example, we'll be using credentials setup in the default profile.

The Ionburst Cloud SDKs can also use a credentials file in your home directory, .ionburst/credentials and, like AWS, can also use environment variables. The Ionburst Cloud .NET SDK documentation gives further detail on how the SDK can be configured.

The starting position

For this tutorial, we'll use a simple program that uploads a file called image.jpg to an S3 bucket, fetches the object from the bucket; storing it locally as image_download.jpg, before removing the object from the bucket.

Our simple program:

using System;
using System.IO;
using System.Threading.Tasks;
using Amazon.S3;
using Amazon.S3.Model;
namespace SimpleUploader
{
class Program
{
static async Task Main(string[] args)
{
StorageInterface storage = new(new AmazonS3Client());
if (await storage.UploadFile("image.jpg"))
{
Console.WriteLine("Uploaded file");
if (await storage.FetchFile("image.jpg"))
{
Console.WriteLine("Fetched file");
if (await storage.RemoveFile("image.jpg"))
{
Console.WriteLine("Removed file");
}
}
}
}
}
public class StorageInterface
{
private IAmazonS3 _storage;
private const string BUCKET_NAME = "ibc-example816";
public StorageInterface(IAmazonS3 storage)
{
_storage = storage;
}
public async Task<bool> UploadFile(string filename)
{
bool result = false;
using (FileStream inputStream = new FileStream(filename, FileMode.Open))
{
PutObjectRequest putRequest = new()
{
BucketName = BUCKET_NAME,
Key = filename,
ContentType = "application/octet-stream",
InputStream = inputStream
};
PutObjectResponse uploadResponse = await _storage.PutObjectAsync(putRequest);
if (uploadResponse.HttpStatusCode == System.Net.HttpStatusCode.OK)
{
result = true;
}
}
return await Task.FromResult(result);
}
public async Task<bool> FetchFile(string filename)
{
bool result = false;
GetObjectRequest getRequest = new()
{
BucketName = BUCKET_NAME,
Key = filename
};
GetObjectResponse fetchResponse = await _storage.GetObjectAsync(getRequest);
if (fetchResponse.HttpStatusCode == System.Net.HttpStatusCode.OK)
{
string outputFile = $"{Path.GetFileNameWithoutExtension(filename)}_download{Path.GetExtension(filename)}";
using (var fileStream = File.Create(outputFile))
{
fetchResponse.ResponseStream.CopyTo(fileStream);
}
result = true;
}
return await Task.FromResult(result);
}
public async Task<bool> RemoveFile(string filename)
{
bool result = false;
DeleteObjectRequest deleteRequest = new()
{
BucketName = BUCKET_NAME,
Key = filename
};
DeleteObjectResponse deleteResponse = await _storage.DeleteObjectAsync(deleteRequest);
if (deleteResponse.HttpStatusCode == System.Net.HttpStatusCode.NoContent)
{
result = true;
}
return await Task.FromResult(result);
}
}
}

The conversion - Configuration

From here, it's simply a case of changing this code to use IBC S6 as the storage element. We'll do this by replacing the AWSSDK.S3 NuGet package with the Ionburst.SDK package.

Once Ionburst.SDK has been added to the project, we can update the using statements, so:

using Amazon.S3;
using Amazon.S3.Model;

becomes

using Ionburst.SDK;
using Ionburst.SDK.Model;

Now we can change the type of our _storage variable and the constructor for our StorageInterface class:

public class StorageInterface
{
private IAmazonS3 _storage;
private const string BUCKET_NAME = "ibc-example;
public StorageInterface(IAmazonS3 storage)
{
_storage = storage;
}

becomes:

public class StorageInterface
{
private IonburstClient _storage;
private const string BUCKET_NAME = "ibc-example";
public StorageInterface(IonburstClient storage)
{
_storage = storage;
}

We then change the instantiation of our StorageInterface class from:

StorageInterface storage = new(new AmazonS3Client());

to

StorageInterface storage = new(new IonburstClient());

Finally, since we won’t be using the AWS credentials file, it is necessary to add an Ionburst section to the project appsettings.json file to define a profile that exists in the Ionburst credentials file:

{
"Ionburst": {
"Profile": "example"
}
}

At this point, there’s going to be some compilation errors to fix and it will be mostly be a case of renaming things. The Ionburst Cloud .NET SDK reference (/sdk/dotnet/) can provide the details of the names you need.

The rest - File operations

If you’re familiar with the Amazon S3 .NET SDK, or indeed other Amazon .NET SDKs, then you know that they follow the same pattern of create and populate a request object; pass that request object as the argument to a function; and receive a response object from the function.

Ionburst.SDK follows the same pattern, it’s just that some names and attributes are different.

The FetchFile and RemoveFile functions are easy to fix, and the fixes are very similar.

The request objects have the same name as their S3 counterparts, but there is no Bucket attribute in the request objects, and Key becomes Particle.

The functions are just GetAsync and DeleteAsync and the response objects become GetObjectResult and DeleteObjectResult.

The StatusCode in the response object is also just an integer instead of a System.Net.HttpStatusCode type. The stream attribute in GetObjectResult is DataStream.

After all that, the functions look like this:

public async Task<bool> FetchFile(string filename)
{
bool result = false;
GetObjectRequest getRequest = new()
{
Particle = filename
};
GetObjectResult fetchResponse = await _storage.GetAsync(getRequest);
if (fetchResponse.StatusCode == 200)
{
string outputFile = $"{Path.GetFileNameWithoutExtension(filename)}_download{Path.GetExtension(filename)}";
using (var fileStream = File.Create(outputFile))
{
fetchResponse.DataStream.Seek(0, SeekOrigin.Begin);
fetchResponse.DataStream.CopyTo(fileStream);
}
result = true;
}
return await Task.FromResult(result);
}
public async Task<bool> RemoveFile(string filename)
{
bool result = false;
DeleteObjectRequest deleteRequest = new()
{
Particle = filename
};
DeleteObjectResult deleteResponse = await _storage.DeleteAsync(deleteRequest);
if (deleteResponse.StatusCode == 200)
{
result = true;
}
return await Task.FromResult(result);
}

The UploadFile function undergoes a similar change. The function call is just PutAsync and the response object is PutObjectResult and the same attribute name changes apply to the request object. Minimally, the request object can be:

PutObjectRequest putRequest = new()
{
Particle = filename,
DataStream = inputStream
};

This will just use the default classification set up for the Ionburst Cloud region selected. Resulting in a function that looks like:

public async Task<bool> UploadFile(string filename)
{
bool result = false;
using (FileStream inputStream = new FileStream(filename, FileMode.Open))
{
PutObjectRequest putRequest = new()
{
Particle = filename,
DataStream = inputStream
};
PutObjectResult uploadResponse = await _storage.PutAsync(putRequest);
if (uploadResponse.StatusCode == 200)
{
result = true;
}
}
return await Task.FromResult(result);
}

From here, we are left with a forlornly unused line that can be removed, as Ionburst Cloud doesn't use the concept of a bucket:

private const string BUCKET_NAME = "ibc-example";

Wrapping up

In this tutorial, we've adapted a simple .NET application using to Amazon S3, to instead use IBC S6 as its storage layer. The conversion itself was quick and easy, taking no longer than a few minutes coding time.

The full converted code can be found below:

using System;
using System.IO;
using System.Threading.Tasks;
using System.Linq;
using Ionburst.SDK;
using Ionburst.SDK.Model;
namespace SimpleUploader
{
class Program
{
static async Task Main(string[] args)
{
StorageInterface storage = new(new IonburstClient());
if (await storage.UploadFile("image.jpg"))
{
Console.WriteLine("Uploaded file");
if (await storage.FetchFile("image.jpg"))
{
Console.WriteLine("Fetched file");
if (await storage.RemoveFile("image.jpg"))
{
Console.WriteLine("Removed file");
}
}
}
}
}
public class StorageInterface
{
private IonburstClient _storage;
private const string BUCKET_NAME = "ibc-example";
public StorageInterface(IonburstClient storage)
{
_storage = storage;
}
public async Task<bool> UploadFile(string filename)
{
bool result = false;
using (FileStream inputStream = new FileStream(filename, FileMode.Open))
{
PutObjectRequest putRequest = new()
{
Particle = filename,
DataStream = inputStream,
};
PutObjectResult uploadResponse = await _storage.PutAsync(putRequest);
if (uploadResponse.StatusCode == 200)
{
result = true;
}
}
return await Task.FromResult(result);
}
public async Task<bool> FetchFile(string filename)
{
bool result = false;
GetObjectRequest getRequest = new()
{
Particle = filename
};
GetObjectResult fetchResponse = await _storage.GetAsync(getRequest);
if (fetchResponse.StatusCode == 200)
{
string outputFile = $"{Path.GetFileNameWithoutExtension(filename)}_download{Path.GetExtension(filename)}";
using (var fileStream = File.Create(outputFile))
{
fetchResponse.DataStream.Seek(0, SeekOrigin.Begin);
fetchResponse.DataStream.CopyTo(fileStream);
}
result = true;
}
return await Task.FromResult(result);
}
public async Task<bool> RemoveFile(string filename)
{
bool result = false;
DeleteObjectRequest deleteRequest = new()
{
Particle = filename
};
DeleteObjectResult deleteResponse = await _storage.DeleteAsync(deleteRequest);
if (deleteResponse.StatusCode == 200)
{
result = true;
}
return await Task.FromResult(result);
}
}
}