Archive for February 2018

Amazon S3 Bucket Management with C#: Part 3 – Listing files in a Bucket

Before getting started

Skill Level: Beginner

Assumptions:

  1. You already gone through Part 1 – Creating a Bucket and Part 2 – Deleting a Bucket

Additional information: I sometimes cover small sub-topics in a post. Along with AWS, you will also be exposed to:

  • async, await, Task
  • Rhyous.SimpleArgs

Step 1 – Add a ListFiles method to BucketManager.cs

  1. Edit file called BucketManager.cs.
  2. Enter this new method:
            public static async Task ListFiles(AmazonS3Client client, string bucketName)
            {
                var listResponse = await client.ListObjectsV2Async(new ListObjectsV2Request { BucketName = bucketName });
                if (listResponse.S3Objects.Count > 0)
                {
                    Console.WriteLine($"Listing items in S3 bucket: {bucketName}");
                    listResponse.S3Objects.ForEach(o => Console.WriteLine(o.Key));
                }
            }
    

    Note: I noticed there was a ListObjectsAsync and a ListObjectsV2Async. I assumed the one with V2 is newer and should be used for new code. The documentation for ListObjectsV2Async confirmed this.

Step 2 – Update the Action Argument

We now need to make this method a valid action for the Action Argument.

  1. Edit the ArgsHandler.cs file to define an Action argument.
                        ...
                        AllowedValues = new ObservableCollection<string>
                        {
                            "CreateBucket",
                            "DeleteBucket",
                            "ListFiles"
                        },
                        ...
    

Notice: We didn’t have a step 3. We wrote some S.O.L.I.D. code in Part 1 and Part 2, which made it really easy for us to implement this method.

Homework: I also read in the documentation that only 1000 files will be listed when a call to ListObjectsV2Async is made. What if you have more than 1000 files, how would you list them all?

Go to: Part 4 – Uploading a file to a Bucket

Return to: Managing Amazon AWS with C#

Amazon S3 Bucket Management with C#: Part 2 – Deleting a Bucket

Before getting started

Skill Level: Beginner

Assumptions:

  1. You already gone through Part 1 – Creating a Bucket where you have already:
    1. Created a new Console Application Project
    2. Added NuGet Package
    3. Created a BucketManager.cs
    4. Coded Program.cs.
    5. Added command line arguments

Additional information: I sometimes cover small sub-topics in a post. Along with AWS, you will also be exposed to:

  • async, await, Task
  • Reflection
  • Rhyous.SimpleArgs
  • Single Responsibility Principal (S of S.O.L.I.D.) or Don’t Repeat Yourself (DRY)
  • Dependency Injection – Method Injection (D of S.O.L.I.D.)

Step 1 – Add a DeleteBucket method to BucketManager.cs

  1. Edit file called BucketManager.cs.
  2. Enter this new method:
            public static async Task DeleteBucket(string bucketName)
            {
                var region = RegionEndpoint.GetBySystemName(ConfigurationManager.AppSettings["AWSRegion"]);
                var client = new AmazonS3Client(region);
                await AmazonS3Util.DeleteS3BucketWithObjectsAsync(client, bucketName);
                Console.WriteLine($"Deleted S3 bucket: {bucketName}");
            }
    

    Notice that there is more involved with deleting a bucket than creating a bucket. A bucket may not be empty. It could have files in it already. Because of this we call a helper method, DeleteS3BucketWithObjectsAsync, that deletes a bucket even if it has objects, i.e. files, in it.

Step 2 – Configure an Argument for the Action to call

Since BucketManager can now can both Create and Delete a bucket, we need an argument to specify what action we would like to call.

  1. Edit the ArgsHandler.cs file to define an Action argument.
    SimpleArgs allows for arguments to be declarative and provides most the features you would want in command line arguments without having to write those features for every new application.

                    new Argument
                    {
                        Name = "Action",
                        ShortName = "a",
                        Description = "The action to run.",
                        Example = "{name}=default",
                        DefaultValue = "Default",
                        AllowedValues = new ObservableCollection&lt;string&gt;
                        {
                            "CreateBucket",
                            "DeleteBucket"
                        },
                        IsRequired = true,
                        Action = (value) =&gt;
                        {
                            Console.WriteLine(value);
                        }
                    }
    

    Notice: We don’t have to validate that the correct method was passed in as a variable because SimpleArgs will do this for us by simply declaring them as AllowedValues. When AllowedValues is declared, only those values are allowed. Any other value will result in the application stopping and outputting the list of valid arguments.

Step 3 – Edit the Program.cs

We are now going to use reflection to call the appropriate function based on our action parameter.

    1. Edit the OnArgumentsHandled method of Program.cs.
              internal static void OnArgumentsHandled()
              {
                  var action = Args.Value("Action");
                  var bucketName = Args.Value("Bucket");
                  var flags = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy;
                  MethodInfo mi = typeof(BucketManager).GetMethod(action, flags);
      
                  var task = mi.Invoke(null, new[] { bucketName }) as Task;
                  task.Wait();
              }
      

Step 4 – Make the code S.O.L.I.D.

We have broken the Single Responsibility Principal (S in S.O.L.I.D.) or Don’t Repeat Yourself (DRY) rule. Let’s notice it and fix it.

    1. Notice in BucketManager.cs that both methods are breaking two rules:
      • Don’t Repeat Yourself or DRY: We have two methods repeating the same two lines of code.
                    var region = RegionEndpoint.GetBySystemName(ConfigurationManager.AppSettings["AWSRegion"]);
                    var client = new AmazonS3Client(region);											
        
      • Single Responsibility Principal: Each method has one repsponsibility. CreateBucket should only create a bucket on the Amazon S3 server. DeletBucket should only delete a bucket. Currently, both methods are having to instantiate a client and figure out the region. That isn’t the responsibility of these methods.
    2. Let’s solve this with Method Injection. Method Injection is a form of Dependency Injection (D in S.O.L.I.D.). We will pass the client into the method.
      Note: I am using method injection because all the methods are static, so Constructor Injection is not an option. Property Injection is an option. A Lazy-injectable Property would also be a very good option here.
    3. Alter the methods to take in an AmazonS3Client object.
      using Amazon.S3;
      using Amazon.S3.Util;
      using System;
      using System.Threading.Tasks;
      
      namespace Rhyous.AmazonS3BucketManager
      {
          public class BucketManager
          {
              public static async Task CreateBucket(AmazonS3Client client, string bucketName)
              {
                  await client.PutBucketAsync(bucketName);
                  Console.WriteLine($"Created S3 bucket: {bucketName}");
              }
      
              public static async Task DeleteBucket(AmazonS3Client client, string bucketName)
              {
                  await AmazonS3Util.DeleteS3BucketWithObjectsAsync(client, bucketName);
                  Console.WriteLine($"Deleted S3 bucket: {bucketName}");
              }
          }
      }
      
    4. Update the Program.cs to instantiate the client and pass it into the methods.
              internal static void OnArgumentsHandled()
              {
                  var action = Args.Value("Action");
                  var bucketName = Args.Value("Bucket");
      
                  var flags = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy;
                  MethodInfo mi = typeof(BucketManager).GetMethod(action, flags);
      
                  var region = RegionEndpoint.GetBySystemName(ConfigurationManager.AppSettings["AWSRegion"]);
                  var client = new AmazonS3Client(region);
      
                  var task = mi.Invoke(null, new object[] { client, bucketName }) as Task;
                  task.Wait();
              }
      

      Notice we now only create a client one time.

      Note: The OnArgumentsHandled in our Program.cs is now doing four things. As you can see above, it has each thing it does in a pair of lines, with each pair of lines separated by a double space. This is where we want to be careful to not overdo it when following design patterns. We only have eight lines of code. Program.cs is supposed to be a program. It is fine for now. Let’s not change it. Notice it. Consider changing it. This time we decided to leave it, but we can keep it in mind. If more lines are added, then that method probably should be changed.

Go to: Part 3 – Listing files in a Bucket

Return to: Managing Amazon AWS with C#

Amazon S3 Bucket Management with C#: Part 1 – Creating a Bucket

Before getting started

Skill Level: Beginner

Assumptions:

  1. You already have Visual Studio installed.
  2. You are familiar with creating projects in Visual Studio.
  3. We assume you have already gone to AWS and registered with them. If you haven’t done that already, stop and go there now. Amazon has a free tier and you can create an account here: https://aws.amazon.com/free

Additional Information: I sometimes cover small sub-topics in a post. Along with AWS, you will also be exposed to:

  • .NET Core 2.0 – If you use .NET Framework, the steps will be slightly different, but as this is a beginner level tutorial, it should be simple.
  • async, await, Task
  • Rhyous.SimpleArgs

Step 1 – Create the project

  1. Open Visual Studio.
  2. Go to File | New Project.
  3. Choose Console Application.
    Give it any name you want.
    I am going to call my project Rhyous.AmazonS3BucketManager.

Step 2 – Add NuGet Packages

  1. Right-click on your project and choose Management NuGet Packages.
  2. Search for AWSSDK.S3.
  3. Install the NuGet package and all the dependencies.
  4. Search for System.Configuration.ConfigurationManager.
  5. Install it.

Step 3 – Create a BucketManager.cs file

  1. Create a new file called BucketManager.cs.
  2. Enter this code:
    using Amazon;
    using Amazon.S3;
    using System;
    using System.Configuration;
    using System.Threading.Tasks;
    
    namespace Rhyous.AmazonS3BucketManager
    {
        public class BucketManager
        {
            public static async Task CreateBucket(string bucketName)
            {
                var region = RegionEndpoint.GetBySystemName(ConfigurationManager.AppSettings["AWSRegion"]);
                var client = new AmazonS3Client(region);
                await client.PutBucketAsync(bucketName);
                Console.WriteLine($"Created S3 bucket: {bucketName}");
            }
        }
    }
    

Step 4 – Edit the Program.cs

  1. Add the following to Program.cs.
            static void Main()
            {
                var task = BucketManager.CreateBucket("my.new.bucket");
                task.Wait();
            }
    

Step 5 – Create/Edit the App.config

  1. If there isn’t an app.config in your project, create one.
  2. Right-click on your project and choose Add | New Item.
  3. Search for Application Configuration File.
    Make sure it is name app.config.
  4. Add an appSetting for your AWS profile name.
    Add an additional appSetting for your chosen AWS region.

    <?xml version="1.0" encoding="utf-8" ?>
    <configuration>
      <appSettings>
        <add key="AWSProfileName" value="yourprofilename"/>
        <add key="AWSRegion" value="us-west-2" />
      </appSettings>
    </configuration>
    

Step 6 – Configure an Argument for the bucket name.

We are going to be adding to this program in subsequent posts. For this reason, we are going to use Rhyous.SimpleArgs library for our command line arguments as it provides ready-made command line argument features.

  1. Install another NuGet Package.
  2. Right-click on your project and choose Management NuGet Packages.
  3. Search for Rhyous.SimpleArgs
  4. Install it.
  5. Create an ArgsHandler.cs file to define the arguments:
    Note: If you used a .NET core project you have to create this file. If you created a .NET Framework file, this file should have been created for you and you have but to edit it.

    using Rhyous.SimpleArgs;
    using System;
    using System.Collections.Generic;
    using System.Text.RegularExpressions;
    
    namespace Rhyous.AmazonS3BucketManager
    {
        public class ArgsHandler : ArgsHandlerBase
        {
            public override void InitializeArguments(IArgsManager argsManager)
            {
                Arguments.AddRange(new List<Argument>
                {
                    new Argument
                    {
                        Name = "Bucket",
                        ShortName = "b",
                        Description = "The bucket name to create. No uppercase or underscores allowed.",
                        Example = "{name}=my.first.bucket",
                        DefaultValue = "my.first.bucket",
                        IsRequired = true,
                        CustomValidation = (value) => 
                        {
                            return Regex.IsMatch(value, "^[a-z0-9.]+$");
                        },
                        Action = (value) =>
                        {
                            Console.WriteLine(value);
                        }
                    }
                });
            }
    
            public override void HandleArgs(IReadArgs inArgsHandler)
            {
                base.HandleArgs(inArgsHandler);
                Program.OnArgumentsHandled();
            }
        }
    }
    
  6. Update Program.cs as follows:
    using Rhyous.SimpleArgs;
    using System;
    
    namespace Rhyous.AmazonS3BucketManager
    {
        class Program
        {
            static void Main(string[] args)
            {
                new ArgsManager<ArgsHandler>().Start(args);
            }
    
            internal static void OnArgumentsHandled()
            {
                var bucketName = Args.Value("Bucket");
                var task = BucketManager.CreateBucket(bucketName);
                task.Wait();
            }
        }
    }
    

Now for fun, you can delete the app.config and change them to parameters.

Next:

  • Deleting a Bucket
  • Return to: Managing Amazon AWS with C#

    I just hit 4000 points on StackOverflow!

    I just hit 4000 points on StackOverflow!


    profile for Rhyous at Stack Overflow, Q&A for professional and enthusiast programmers