In this blog post I will play around with Amazon AWS S3 and .NET Core 3.0. I will check how .NET Core is getting along with the most popular cloud platform.
Amazon S3.
Amazon Simple Storage Service (Amazon S3) is an object storage service that offers industry-leading scalability, data availability, security, and performance. This means customers of all sizes and industries can use it to store and protect any amount of data for a range of use cases, such as websites, mobile applications, backup and restore, archive, enterprise applications, IoT devices, and big data analytics. Amazon S3 provides easy-to-use management features so you can organize your data and configure finely-tuned access controls to meet your specific business, organizational, and compliance requirements.
See here for more!
.NET Core.
.NET Core is an open-source, general-purpose development platform maintained by Microsoft and the .NET community on GitHub. It’s cross-platform (supporting Windows, macOS, and Linux) and can be used to build device, cloud, and IoT applications.
See here for more!
Let’s start.
In my personal opinion, .NET Core is the best development stack. With .NET Core 3.0 desktop stacks (WPF and Windows Forms) will be included.
Amazon with its Web Services is the biggest cloud service provider. I already did some projects with Amazon S3 and tons of .NET-based projects. I was really eager to try, play around and see how these two technologies play along together.
Being early adopter, I needed to try .NET Core 3 Preview 7 & (latest version at the time of this writing) with Windows Forms (also new desktop development stack introduced in NET Core 3.0). I tried to check how these two technologies/frameworks play along with AWS S3.
To start with, first, I prepared everything on Amazon server-side.
AWS S3 access keys.
I log-in into my AWS Console (https://console.aws.amazon.com/console/home) and I created new access ID/Key for my test app.
For the purpose of my testing “Access Key/Secret key” credential option is fine, but using IAM user credential option is better because it provides several benefits, just to mention few: programmatic/console access, better tuning of access rules, grouping, option to MFA, auto managing of permissions, etc… As said, for the purpose of this testing I used Access Keys option.
.NET Core 3.0 & Windows Forms.
Windows Forms desktop stack and .NET Core 3.0 are still in preview (Preview 7), so I expected some problems. First, I created new .NET Core 3 (Preview 7) Windows Forms application in my Visual Studio 2019. First problem popped-up very quickly: Visual Studio 2019 Windows Forms designer was crashing. I got this error:
After some investigation, I found out that is known issue. I overcome this by designing Windows Forms GUI elements in parallel .NET Framework application and then just copied generated code in my .NET Code app.
After having all in place, my demo application looked similar to this:
GUI was ready, so I needed to start putting in some “logic”. First, I included some NuGet dependencies:
First two AWSDK.***
dependencies are Amazon S3 SDK packages for working with AWS S3 services. Microsoft.Extensions.Configuration.Json
is package containing JSON configuration extensions. Because I really like Dependency Injection approach, I also used Microsoft.Extensions.DependencyInjection
extension for that purposes.
My application entry point looked like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
static class Program { [STAThread] static void Main() { var configuration = new ConfigurationBuilder() .AddJsonFile("appsettings.json") .Build(); var servicesContainer = new ServiceCollection(); servicesContainer.AddSingleton<IConfiguration>(configuration); servicesContainer.AddAWSService<IAmazonS3>(); servicesContainer.AddSingleton<AmazonBucketTestingForm>(); // apply app styles Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); var serviceProvider = servicesContainer.BuildServiceProvider(); var entryForm = (Form)serviceProvider.GetService(typeof(AmazonBucketTestingForm)); Application.Run(entryForm); } } |
Let’s check what is going on in my application bootstrapping phase:
- From line 6 to 8, configuration file is processed and handled. This config file processed, contains AWS basic info, e.g.
1234567{"AWS": {"Profile": "local-test-profile","Region": "eu-central-1","ProfilesLocation": "c:\\temp\\profileData.txt"}}
- From line 10 to 13 services registration is executed.
- Line 16 and 17 apply default styles to Windows Forms application.
- Line 20 and 21 start the application.
For the purpose of this demo, my AWS S2 access credentials were put into JSON file on my local filesystem (c:\temp\profileData.txt). This file contain AWS credential information (check paragraph AWS S3 access keys).
1 2 3 |
[local-test-profile] aws_access_key_id=AKIAxxxxxxxxxxxxxxxx aws_secret_access_key=jv7qxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx |
When doing production ready code, this file/data should be safely stored in some secure storage.
I injected IAmazonS3
service into Form constructor in order to use it in my prototype. Here, I need to mention, that demo app is not perfectly designed (or better yet: has no design at all :), but skilled observer with little imagination can quickly see how this form class could be transformed into some custom AWS client service, and injected all over the application. Here, I focused only on core functionality.
I injected IAmazonS3
interface into my main form like this:
1 2 3 4 5 6 7 8 9 10 11 |
public partial class AmazonBucketTestingForm : Form { private readonly IAmazonS3 _amazonS3; public AmazonBucketTestingForm(IAmazonS3 amazonS3) { _amazonS3 = amazonS3; InitializeComponent(); } ... //source code removed for brevity |
Basic AWS S3 scenarios.
Create empty bucket.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
private async void CreateEmptyBucketTestButton_Click(object sender, EventArgs e) { try { // create ad-hoc unique bucket name. var newBucketName = "test-bucket-" + Guid.NewGuid(); // create... var actionResponse = await _amazonS3.PutBucketAsync(newBucketName); // display what happened... if (actionResponse.HttpStatusCode == HttpStatusCode.OK) WriteLogLine($@"New {newBucketName} created."); else WriteLogLine($@"Something went wrong when creating new ""{newBucketName}"" bucket."); } catch (AmazonS3Exception amazonS3Exception) { WriteLogLine($@"S3 error occurred. Exception: {amazonS3Exception}."); } catch (Exception ex) { WriteLogLine($@"Exception: {ex}."); } } |
My testing application:
Result on AWS S3 storage:
Create empty bucket and upload file.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
private async void CreateEmptyBucketAndUploadTestButton_Click(object sender, EventArgs e) { try { // new aws bucket name... var newBucketName = "test-bucket-" + Guid.NewGuid(); // pickup local file to be uploaded... var localDir = Assembly.GetExecutingAssembly().GetDirectoryPath(); var filename = "TestFile.txt"; var localFileNamePath = Path.Combine(localDir, filename); // create aws bucket first... var bucketCreationResponse = await _amazonS3.PutBucketAsync(newBucketName); if (bucketCreationResponse.HttpStatusCode != HttpStatusCode.OK) WriteLogLine($@"New {newBucketName} creation failed, exiting."); // start uploading selected file... var request = new TransferUtilityUploadRequest(); using var utility = new TransferUtility(_amazonS3); request.BucketName = newBucketName; request.Key = filename; request.FilePath = localFileNamePath; await utility.UploadAsync(request); WriteLogLine($@"File ""{filename}""uploaded to newly created {newBucketName} bucket."); } catch (AmazonS3Exception amazonS3Exception) { WriteLogLine($@"S3 error occurred. Exception: {amazonS3Exception}."); } catch (Exception ex) { WriteLogLine($@"Exception: {ex}"); } } |
My testing application:
Result on AWS S3 storage:
Create & Delete bucket.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
private async void CreateAndDeleteEmptyBucketTestButton_Click(object sender, EventArgs e) { try { // create ad-hoc unique bucket name. var newBucketName = "test-bucket-" + Guid.NewGuid(); // create... var createActionResponse = await _amazonS3.PutBucketAsync(newBucketName); // display what happened... if (createActionResponse.HttpStatusCode == HttpStatusCode.OK) { WriteLogLine($@"New {newBucketName} created."); } else { WriteLogLine($@"Something went wrong when creating new ""{newBucketName}"" bucket."); return; } WriteLogLine($@"Waiting 5 sec and then deleting {newBucketName} bucket."); await Task.Delay(5000); var deleteActionResponse = await _amazonS3.DeleteBucketAsync(newBucketName); if (deleteActionResponse.HttpStatusCode == HttpStatusCode.NoContent) // here extra check is needed!! WriteLogLine($@"{newBucketName} deleted."); else WriteLogLine($@"Something went wrong when deleting ""{newBucketName}"" bucket."); } catch (AmazonS3Exception amazonS3Exception) { WriteLogLine($@"S3 error occurred. Exception: {amazonS3Exception}."); } catch (Exception ex) { WriteLogLine($@"Exception: {ex}."); } } |
My testing application:
Bucket content listing
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
private async void GetBucketSubfolderTestButton_Click(object sender, EventArgs e) { try { var request = new ListObjectsV2Request {BucketName = "jenx-test-bucket", MaxKeys = 10}; ListObjectsV2Response response; do { response = await _amazonS3.ListObjectsV2Async(request); foreach (var entry in response.S3Objects) WriteLogLine($@"key = {entry.Key} size = {entry.Size}, {entry.StorageClass}"); request.ContinuationToken = response.NextContinuationToken; } while (response.IsTruncated); } catch (AmazonS3Exception amazonS3Exception) { WriteLogLine($@"S3 error occurred. Exception: {amazonS3Exception}."); } catch (Exception ex) { WriteLogLine($@"Exception: {ex}."); } } |
My testing application:
AWS S3 storage:
More on AWS SDK for .NET.
Amazon.S3.IAmazonS3
interface has very rich API for AWS S3. With similar approach as presented above, you can extend usage on very complex scenarios/cases.
In this blog post, I presented only AWS S3 component of AWS SDK for .NET. This SDK can also be used for other Amazon services: Amazon DynamoDB, Amazon Glacier, Batch, ElastiCache, etc…
AWS SDK for .NET is open source with Apache 2.0 license. You can check out source code at https://github.com/aws/aws-sdk-net.
Furthermore, Amazon has awesome documentation on AWS SDK for .NET: https://aws.amazon.com/sdk-for-net.
AWS SDK for .NET target multiple platforms including: Windows Store, Windows Phone, and Xamarin on iOS and Android.
Conclusion.
Lately, I tested a lot of things with .NET Core 3.0 (different preview versions) – and amazingly almost everything I tested just worked. OK, Visual Studio has some Windows Forms designer issues, but this will fixed when .Net Core 3.0 will be released.
I would like to mentioned also that Amazon has super support for developing .NET application targeting AWS cloud services. Latest AWS SDK is very modern and feature-rich.
You can download code for this experiment here: https://github.com/josipx/Jenx.Aws.Playground