Why long emails should be avoided as a Dev Lead

I keep failing to a avoid a common mistake as a leader. Sending long emails. It seems so easy. For whatever reason, as the dev lead, I cannot talk to a person face-to-face so I write a long email.

I could spend time talking about why email is bad, or I could show you how emails make people feel by showing you an email dialogue.

Why long emails should be avoided:

Dev Lead: I’m being a good mentor. Write a nice long email that will help the team grow on a subject A, that includes tons of info on Subject A, including its 5 benefits. I send this email to Dev1 and CC the other two members of my team.
Feels good about his leadership.

Dev 1: What the dev thinks: Uh, oh. The dev lead is having a hissy fit again. Looks like he is pissed at something I did. What a jerk.
Feels angry.

Dev 2: Oh no. I have no idea what they talking about. Do I know my stuff. Googles and tries to learn what the dev lead is talking about.
Feels shamed.

Dev 3: Ugh! Why is he trying to teach me crap I already know.
Feels patronized.

Manager: Hey, the team didn’t appreciate that email.

Dev Lead: Feels like a poor leader.

Manager: Feels like he is losing his team.

 

Why it would have happened better face-to-face:

Dev Lead: Hey devs. I want to discuss subject A. What do you know about it already?

Dev 1: I’ve used it before

Dev 2: Stays silent.

Dev 3: I know all about Subject A.

Dev Lead: OK, Dev 3, tell us about subject A.

Dev 3: Gives four excellent points about subject A. One of them the dev lead didn’t know.

Dev Lead: Adds two points about subject A that Dev 3 didn’t know. Changes his list from 5 to 6 adding the one item Dev 3 didn’t know.
Feels impressed by Dev 3.

Dev 1: Feels growth.

Dev 2: Feels good to be introduced to a new subject.

Dev 3: Impressed that the dev lead let him educate the team.
Feels more respect for dev lead. Also notes that the Dev Lead knew things he didn’t and thinks he should listen more.

Manager: Feels good about the team.

 

It is all about the feelings, and there is something about face-to-face team interaction that leads to good feelings and something about long emails that always leads to bad feelings.

So, if you look at the face-to-face interaction, you can see that it all started with a short question. You could simulate this in a short email:

Dev Lead: Who can give me all the benefits of Subject A using only the knowledge in your head. No browser search allowed until after you respond.

Dev 1: Responds with the single most common benefit if subject A.

Dev 2: Doesn’t respond.

Dev 3: Responds with four items, one that the dev lead didn’t now about.

Dev Lead: Interesting. Here are the items that the team responded with. I added two more benefits for a total of 6. Should we use subject A to get those 6 benefits in our project?

 

Now imaging the response was crickets.

Dev Lead: Who can give me all the benefits of Subject A.

Dev 1: Doesn’t respond.

Dev 2: Doesn’t respond.

Dev 3: Responds with one item.

Dev Lead: Subject A is interesting and important to our project. I am going to create a quick training on it.

Dev Lead: Writes a doc on it and sends it to the team.

Team: Feels good to learn something new.

Manager: Feels like the team is running itself.

Tips

  1. Keep emails short.
  2. Use many short emails.
  3. Ask questions, preferable one-liners:
    1. Start by asking your team what they already know first.
    2. Ask follow-up questions second
  4. Compile responses into a bulleted list
    1. Add to the list if you can
    2. Ask questions about the list
  5. Thank the team

I am going to put these tips into practice next time I feel like sending a long email.


Code Review – Quick Reference

This is a simple check-list to make code reviews more valuable. Simply check these rules.

Download a single page word document: Code Review Cheat Sheet

Does the code follow the 10/100 Rule?

This is a quick check rule that isn’t extremely rigid. See the 10/100 rule of code

Method has less than 10 lines

Is the method that was added or changed 10 lines or less? (There are always exceptions such as Algorithms)

100

Is the class 100 lines or less?
Note: Model classes should have zero functions closer to 20 lines. Logic classes should be sub-100 lines.

Is the code S.O.L.I.D.

S.O.L.I.D. is an acronym. See this link: https://en.wikipedia.org/wiki/SOLID

Single Responsibility Principal

Does each class have a single responsibility? Does each method have a single responsibility?
Is this the only class that has this responsibility? (No duplicate code or D.R.Y. (Don’t Repeat Yourself)

Open/Closed Principle

Can you extend the functionality without modifying this code? Config, Plugins, event registration, etc.
Is there configuration is this code? If so, extract it. Configuration does not belong in code.

Liskov substitution principal

Is inheritance used? If so, does the child type cause issues the parent type wouldn’t cause?

Interface segregation principle

Does the code use interface-based design?
Are the interfaces small?
Are all parts of the interface implementations without throwing a NotImplementedException?

Dependency inversion principle

Does the code reference only interfaces and abstractions?
Note: If new code references concrete classes with complex methods, it is coded wrong.

Is the code Unit Tested

99% coverage

Is the Code 99% covered? Is code not covered marked with the ExcludeFromCodeCoverageAttribute?

Parameter Value Tests for methods with parameters

Are all parameter values that could cause different behavior covered?
See these links:
Unit testing with Parameter Value Coverage (PVC)
Parameter Value Coverage by type

Naming things

Typos

Are your names typo free?

Naming convention

Do your file names, class names, method names, variable names match existing naming conventions?

Big O

Do you have any glaringly obvious Big O problems? n or n2 vs when it could be constant or log n.
See: https://en.wikipedia.org/wiki/Big_O_notation


Parameter Value Coverage by Type

This article is a reference to Unit Testing with Parameter Value Coverage (PVC).

Primitive or Value Types

See this reference.

Short Name.NET ClassTypeWidthRange (bits)
byteByteUnsigned integer80 to 255
sbyteSByteSigned integer8-128 to 127
intInt32Signed integer32-2,147,483,648 to 2,147,483,647
uintUInt32Unsigned integer320 to 4294967295
shortInt16Signed integer16-32,768 to 32,767
ushortUInt16Unsigned integer160 to 65535
longInt64Signed integer64-9223372036854775808 to 9223372036854775807
ulongUInt64Unsigned integer640 to 18446744073709551615
floatSingleSingle-precision floating point type32-3.402823e38 to 3.402823e38
doubleDoubleDouble-precision floating point type64-1.79769313486232e308 to 1.79769313486232e308
charCharA single Unicode character16Unicode symbols used in text
boolBooleanLogical Boolean type8True or false
objectObjectBase type of all other types
stringStringA sequence of characters
decimalDecimalPrecise fractional or integral type that can represent decimal numbers with 29 significant digits128±1.0 × 10e−28 to ±7.9 × 10e28

byte

  1. Zero, 0, which is also byte.MinValue.
  2. A positive byte between 0 and 255.
  3. byte.MaxValue or 255

sbyte

  1. Zero, 0, which is also sbyte.MinValue.
  2. A positive sbyte between 0 and 127.
  3. A negative sbyte between -128 and 0.
  4. sbyte.MaxValue or 127
  5. sbyte.MinValue or -128

int

  1. A positive int between 0 and 2,147,483,647
  2. A negative int between -2,147,483,648 and 0
  3. Zero, 0
  4. int.MaxValue or 2,147,483,647
  5. int.MinValue or -2,147,483,648

uint

  1. Zero, 0, which is also uint .MinValue.
  2. A positive uint between 0 and 4,294,967,295.
  3. uint .MaxValue or 4,294,967,295

short

  1. A positive short between 0 and 32,767
  2. A negative short between -32,768 and 0
  3. Zero, 0
  4. short.MaxValue or 32,767
  5. short.MinValue or -32,768

ushort

  1. Zero, 0, which is also ushort .MinValue.
  2. A positive ushort, such as 1 through 65,535.
  3. ushort.MaxValue or 65,535

long

  1. A positive long between 0 and 9,223,372,036,854,775,807
  2. A negative long between -9,223,372,036,854,775,808 and 0
  3. Zero, 0
  4. long.MaxValue or 9,223,372,036,854,775,807
  5. long.MinValue or -9,223,372,036,854,775,808

ulong

  1. Zero, 0, which is also ulong.MinValue.
  2. A positive ulong between 0 and 18,446,744,073,709,551,615.
  3. ulong.MaxValue or 18,446,744,073,709,551,615

float

  1. A positive float between 0 and 3.402823E+38
    1. Note: This includes the float.Epsilon, but you could test double.Epsilon separately
  2. A negative float between -3.402823E+38 and 0
  3. Zero, 0.0
  4. float.MaxValue or 3.402823E+38
  5. float.MinValue or -3.402823E+38
  6. float.NaN
  7. float.PositiveInfinity
  8. float.NegativeInfinity

double

  1. A positive double between 0 and 1.79769313486232E+308
    1. Note: This includes the double.Epsilon, but you could test double.Epsilon separately
  2. A negative double between -1.79769313486232E+308 and 0
  3. Zero, 0.0
  4. double.MaxValue or 1.79769313486232E+308
  5. double.MinValue or -1.79769313486232E+308
  6. double.NaN
  7. double.PositiveInfinity
  8. double.NegativeInfinity

decimal

  1. A positive double between 0 and 79,228,162,514,264,337,593,543,950,335
  2. A negative double between -79,228,162,514,264,337,593,543,950,335 and 0
  3. Zero, 0
  4. double.MaxValue or 79,228,162,514,264,337,593,543,950,335
  5. double.MinValue or -79,228,162,514,264,337,593,543,950,335

string

  1. A null string
  2. An empty string, String.Empty, or “”
  3. One or more spaces ” “
  4. One or more tabs ” “
  5. A new line or Environment.NewLine
  6. A valid string.
  7. An invalid or junk string
  8. A string with many special characters: `~!@#$%^&*()_-+=,.<>/\?[]{}|
  9. Unicode characters such as Chinese
  10. An long string, over 256 characters, or even 1 million characters.
  11. (Occasionally) Case sensitivity. For example, for string comparisons, case sensitivity of a string is a required Parameter Value Coverage test.

Struct

  1. It is impossible to know. You need to define this per struct you create. For example, if your struct is a point with int values X and Y, then it is simply the int list above twice, once for X and once for Y.

Enum

  1. Any of the enums.
  2. You may need to do each of the enums, depending on how your enum is used.

 

Class or Reference Types

Class Object

Objects that are defined with the class keyword need the following tested:

  1. Null (This might go away or become optional in .NET 4.8)
  2. Instantiated
  3. Class properties can be primitive or value types, reference types, etc., and may need to be tested according to the type of the property.

Array, List, Dictionary, and other collections

Array, List, Collection

  1. Null
  2. Empty (instantiated with no items)
  3. Not empty but values of array are tested according to the value type. For example, an int[] would need to have the values tested in the ways listed above for int.

Dictionary

  1. Null
  2. Empty (instantiated with no items)
  3. Key exists
  4. Key doesn’t exist
  5. Value at key is tested according to its value type. For example, a Dictionary<string, int> would need to have the values tested in the ways listed above for int.

Amazon Ec2 Instance Management with C#: Part 3 – Uploading and Importing a Key Pair

Before getting started

Skill Level: Beginner

Assumptions:

  1. You have completed Part 1 and 2 of Managing Amazon AWS with C# – EC2

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.
  • Rhyous.SimpleArgs

Details

We may already have a key pair that we want to use, so we don’t want to create a new one. If that is the case, it can be uploaded.

Step 1 – Get key in the correct format

I used OpenSSL to do this.

  1. Download OpenSSL.
  2. Run this command:
    [sh]
    .\openssl.exe rsa -in c:\users\jbarneck\desktop\new.pem -pubou
    t -out c:\users\jbarneck\desktop\new.pub
    [sh]

Step 2 – Edit InstanceManager.cs file

We’ve created InstanceManager.cs in Part 1. Let’s edit it.

  1. Add a method to read the key file from disk and upload and import the key pair.
  2.         public static async Task ImportKeyPair(AmazonEC2Client ec2Client, string keyName, string keyFile)
            {
                var publicKey = File.ReadAllText(keyFile).Trim().RemoveFirstLine().RemoveLastLine();
                string publicKeyAsBase64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(publicKey));
                await ec2Client.ImportKeyPairAsync(new ImportKeyPairRequest(keyName, publicKeyAsBase64));
            }
    

Notice: We are calling RemoveFirstLine() and RemoveLastLine(); This is because key files have a header and footer that must be removed before sending up to AWS. We’ll do this in the next section.

Step 3 – Add methods RemoveFirstLine and RemoveLastLine

  1. By the time this publishes, you should only need to install Rhyous.String.Library. Otherwise, add this class file:
    namespace Rhyous.AmazonEc2InstanceManager
    {
        public static class StringExtensions
        {
            public static string RemoveFirstLine(this string text, char newLineChar = '\n')
            {
                if (string.IsNullOrEmpty(text))
                    return text;
                var i = text.IndexOf(newLineChar);            
                return i > 0 ? text.Substring(i + 1) : "";
            }
    
            public static string RemoveLastLine(this string text, char newLineChar = '\n')
            {
                var i = text.LastIndexOf(newLineChar);
                return (i > 0) ? text.Substring(0, i) : "";
            }
        }
    }
    

Step 4 – Configure command line Arguments.

We already have an Actions arguments to edit.

  1. Add DeleteKeyPair as a valid action to the Action argument.
  2. Add an additional argument for the key file.
                . . .
                new Argument
                {
                    Name = "Action",
                    ShortName = "a",
                    Description = "The action to run.",
                    Example = "{name}=default",
                    DefaultValue = "Default",
                    AllowedValues = new ObservableCollection<string>
                    {
                        "CreateKeyPair",
                        "DeleteKeyPair",
                        "ImportKeyPair"
                    },
                    IsRequired = true,
                    Action = (value) =>
                    {
                        Console.WriteLine(value);
                    }
                },
                . . .
                new Argument
                {
                    Name = "KeyFile",
                    ShortName = "pem",
                    Description = "The full path to a public key already created on your file system in PEM format. The full Private key won't work.",
                    Example = "{name}=c:\\My\\Path\\mykeyfile.pem",
                    CustomValidation = (value) => File.Exists(value),
                    Action = (value) =>
                    {
                        Console.WriteLine(value);
                    }
                }

You can now upload a public key file for use on the Amazon Cloud.

Next: Part 4

Return to: Managing Amazon AWS with C#


Interviewing: A developer should have a portfolio

I recently started interviewing for some contract positions, one a Software Developer in Test position and one a Senior Software Developer position. I am deeply surprised by the candidates complete lack of having an online presence. As I thought more about this, I realized that we have reached a point of maturity in the Software Developer roles that portfolios are now expected. I expected every candidate to have an active account on some open source source control repository, i.e. GitHub, and have a portfolio of code there.

Portfolio

When it is time to interview for a position as a developer, you should have a portfolio. The days of coding on a whiteboard should be over. Instead, an interviewer should be able to easily see your code and what you have or haven’t done.

There shouldn’t be a question about whether you can write code. Instead, the question should be: Based on the code we can see this individual has written, can they be a good fit for our team?

Proprietary code

Your portfolio cannot include proprietary code. End of discussion. If you are a developer and you can’t find code that isn’t proprietary to put into your portfolio, then what are you doing?

Open Source/Non-proprietary code

Even when working with proprietary code, there are many pieces of code that are so ubiquitous that they probably should be part of the .NET framework. You may use this code in every project you work on. Such as common string extensions in C#, or a more complete string check in javascript that checks if a string is undefined, null, empty, or whitespace.

Even better is if your code is not just stored, but it is available to be used, such as with NuGet, npm, Maven, or other code or library packaging tool. This shows that you not only have a portfolio, but you aren’t going to waste your hours rewriting code you have already written.

Where to keep your portfolio

I used to have mine on SourceForge but have since switched to GitHub. Visual Studio online is another option. Where you store your portfolio of your work does not matter as much as the fact that you do store it.

GitHub is where I chose. But you can easily Google for GitHub competitors if you want it to be elsewhere.

Brand your portfolio

My internet handle is Rhyous. Every piece of code I write that is part of my portfolio (Non-proprietary or not for someone else’s open source project) is now branded with Rhyous. Some of my older code may not be, but my new code is. For example, all my namespaces in C# now start with Rhyous. That makes it very easy to differentiate projects I have forked vs projects that I have developed.

What your portfolio must show

It must show:

  • You have skills as a developer.
  • SOLID principals.
  • An understanding of the importance of Unit Tests.
  • You refuse to waste time writing the same code twice.**
  • You can work on projects with other developers.
  • You bring more than just your skill set, you bring your ready-made building blocks.

** I find this to be so very important!

My Portfolio

My portfolio shows my skills as a developer. My code uses SOLID principals. Much of my code is Unit Tested.

I don’t like to write the same code twice. I for one, will never have to write a CSV parser in C# again as I have a good quality one: Rhyous.EasyCsv. Parsing arguments? I’ll never write an argument parser again because I have Rhyous.SimplArgs. I will never have to write many of my string extensions again as I can easily grab them for any C# project from my Rhyous.StringLibrary NuGet package. Tired of using TryGetValue to get values from your dictionary? Try Rhyous.Collections and use the NullSafe dictionary, which still used the TryGetValue but moves it inside the indexer so you don’t have to worry about it.

What a lack of portfolio shows

It has never clicked for you. What I mean by “It” is the idea of code reuse. The idea of object oriented programming. The idea of standing on the shoulders of giants. The simple idea of using building blocks as a kid and make things from building block.

Go out and make your portfolio and fill it with building blocks so every time you build something new, you can build on foundation building blocks that are SOLID and just get better.


Amazon Ec2 Instance Management with C#: Part 2 – Deleting a Key Pair

Before getting started

Skill Level: Beginner

Assumptions:

  1. You have completed Part 1 of Managing Amazon AWS with C# – EC2

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.
  • Rhyous.SimpleArgs

Step 1 – Edit InstanceManager.cs file

We’ve created InstanceManager.cs in Part 1. Let’s edit it.

  1. Add a method to delete the key pair.
  2.         public static async Task DeleteKeyPair(AmazonEC2Client ec2Client, string keyName)
            {
                await ec2Client.DeleteKeyPairAsync(new DeleteKeyPairRequest { KeyName = keyName });
            }
    

Step 5 – Configure command line Arguments.

We already have an Actions arguments to edit.

  1. Add DeleteKeyPair as a valid action to the Action argument.
  2.                 . . .
                    new Argument
                    {
                        Name = "Action",
                        ShortName = "a",
                        Description = "The action to run.",
                        Example = "{name}=default",
                        DefaultValue = "Default",
                        AllowedValues = new ObservableCollection<string>
                        {
                            "CreateKeyPair",
                            "DeleteKeyPair"
                        },
                        IsRequired = true,
                        Action = (value) =>
                        {
                            Console.WriteLine(value);
                        }
                    },
                    . . .
    

Next:

  • Part 3 – Uploading and Importing a Key Pair
  • Return to: Managing Amazon AWS with C#


    Amazon Ec2 Instance Management with C#: Part 1 – Creating a Key Pair

    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
    4. Some of the points that were discussed in the Amazon S3 article series will not be repeated here.

    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
    • Reflection

    Note: As this is the first post of a series, there is going to be some setup. If you just want the quick and code, look at the code in Step 3.

    EC2 Instance Prerequisites

    Before creating an instance, there are some prerequisites. This Part 1 discusses the first of these prerequisites: creating a Key pair.

    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.AmazonEc2InstanceManager.

    Step 2 – Add NuGet Packages

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

    Step 3 – Create an InstanceManager.cs file

    1. Create a new class file called InstanceManager.cs.
    2. Add a method to generate the key pair.
    3. Add a method to save the key pair to disc as a .pem file.
    4. using Amazon.EC2;
      using Amazon.EC2.Model;
      using System;
      using System.Collections.Generic;
      using System.IO;
      using System.Threading.Tasks;
      
      namespace Rhyous.AmazonEc2InstanceManager
      {
          public class InstanceManager
          {
              public static async Task&amp;lt;KeyPair&amp;gt; CreateKeyPair(AmazonEC2Client ec2Client, string keyName, bool writePem = false, string keyOutputDirectory = null)
              {
                  var keyPair = (await ec2Client.CreateKeyPairAsync(new CreateKeyPairRequest { KeyName = keyName }))?.KeyPair;
                  if (writePem)
                      await SaveKeyPairToDisc(keyName, keyOutputDirectory, keyPair);
                  return keyPair;
              }
      
              public static async Task SaveKeyPairToDisc(string keyName, string keyOutputDirectory, KeyPair keyPair)
              {
                  var path = Path.Combine(keyOutputDirectory, $&amp;quot;{keyName}.pem&amp;quot;);
                  await File.WriteAllTextAsync(path, keyPair.KeyMaterial);
                  Console.WriteLine($&amp;quot;They key pair was saved to: {path}&amp;quot;);
              }
          }
      }
      

      Notice: To create the Key pair, we use an instance of an AmazonEC2Client with a simple CreateKeyPairRequest model.
      Notice: Saving the Key pair has really nothing to do with Amazon EC2 Instance management. This is just a simple File.WriteAllTextAsync call.

    Step 4 – Create/Edit the App.config

    Of course, it is important to know what Amazon Web Services account we are working with, so we will store this in 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.
      Rename it to app.config.
    4. Add an appSetting for your AWS profile name.
    5. Add an additional appSetting for your chosen AWS region.
      &amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;utf-8&amp;quot; ?&amp;gt;
      &amp;lt;configuration&amp;gt;
        &amp;lt;appSettings&amp;gt;
          &amp;lt;add key=&amp;quot;AWSProfileName&amp;quot; value=&amp;quot;yourprofilename&amp;quot;/&amp;gt;
          &amp;lt;add key=&amp;quot;AWSRegion&amp;quot; value=&amp;quot;us-west-2&amp;quot; /&amp;gt;
        &amp;lt;/appSettings&amp;gt;
      &amp;lt;/configuration&amp;gt;
      

    Step 5 – Configure command line Arguments.

    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. If you have been through the S3 series, you will already be familiar with Arguments and the convention we chose to match command line arguments to the method parameter names.
    Note: You should have already installed the Rhyous.SimpleArgs NuGet package.

    1. 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.
    2. Add an Action argument.
      Note: We are going to keep the exact same convention of having an Action argument that allows us to choose which method to run and for each method and have an Argument to match each method parameter name.

    3. Add an Argument for each dynamic parameter in the CreateKeyPair method.
      • string keyName
      • string keyOutputDirectory

      Note: The AmazonEC2Client parameter is not a dynamic parameter. It is fixed and doesn’t change.

    4. Also, make sure to call Program.OnArgsHandled() in the HandleArgs method.
    5. using Amazon.EC2.Util;
      using Rhyous.SimpleArgs;
      using System;
      using System.Collections.Generic;
      using System.Collections.ObjectModel;
      
      namespace Rhyous.AmazonEc2InstanceManager
      {
          public class ArgsHandler : ArgsHandlerBase
          {
              public override void InitializeArguments(IArgsManager argsManager)
              {
                  Arguments.AddRange(new List&amp;lt;Argument&amp;gt;
                  {
                      new Argument
                      {
                          Name = &amp;quot;Action&amp;quot;,
                          ShortName = &amp;quot;a&amp;quot;,
                          Description = &amp;quot;The action to run.&amp;quot;,
                          Example = &amp;quot;{name}=default&amp;quot;,
                          DefaultValue = &amp;quot;Default&amp;quot;,
                          AllowedValues = new ObservableCollection&amp;lt;string&amp;gt;
                          {
                              &amp;quot;CreateKeyPair&amp;quot;
                          },
                          IsRequired = true,
                          Action = (value) =&amp;gt;
                          {
                              Console.WriteLine(value);
                          }
                      },
                      new Argument
                      {
                          Name = &amp;quot;KeyName&amp;quot;,
                          ShortName = &amp;quot;k&amp;quot;,
                          Description = &amp;quot;A key pair name.&amp;quot;,
                          Example = &amp;quot;{name}=MyKeyPair&amp;quot;,
                          Action = (value) =&amp;gt;
                          {
                              Console.WriteLine(value);
                          }
                      },
                      new Argument
                      {
                          Name = &amp;quot;keyOutputDirectory&amp;quot;,
                          ShortName = &amp;quot;k&amp;quot;,
                          Description = &amp;quot;A key pair name.&amp;quot;,
                          Example = &amp;quot;{name}=MyKeyPair&amp;quot;,
                          Action = (value) =&amp;gt;
                          {
                              Console.WriteLine(value);
                          }
                      }
                  });
              }
      
              public override void HandleArgs(IReadArgs inArgsHandler)
              {
                  base.HandleArgs(inArgsHandler);
                  Program.OnArgumentsHandled();
              }
          }
      }
      

    Step 6 – Add the MethodInfoExtension

    This has nothing to do with AWS, but we built it in the S3 series, so were are going to use it for this series as well. It is a class file that uses reflection to dynamically detect the needed parameters of our action methods. I am not going to go into it any more that that in this post. I’ve modified this file to work for this project. Just add it to your project. If you want to understand it more, go through the S3 posts.

    1. Create a MethodInfoExtensions.cs.
      using Amazon;
      using Amazon.EC2;
      using Rhyous.SimpleArgs;
      using System;
      using System.Collections.Generic;
      using System.Configuration;
      using System.Reflection;
      
      namespace Rhyous.AmazonEc2InstanceManager
      {
          public static class MethodInfoExtensions
          {
              public static List&amp;lt;object&amp;gt; DynamicallyGenerateParameters(this MethodInfo mi)
              {
                  var parameterInfoArray = mi.GetParameters();
                  var parameters = new List&amp;lt;object&amp;gt;();
                  var region = RegionEndpoint.GetBySystemName(ConfigurationManager.AppSettings[&amp;quot;AWSRegion&amp;quot;]);
                  foreach (var paramInfo in parameterInfoArray)
                  {
                      if (paramInfo.ParameterType == typeof(AmazonEC2Client))
                          parameters.Add(Activator.CreateInstance(paramInfo.ParameterType, region));
                      if (paramInfo.ParameterType == typeof(string))
                          parameters.Add(Args.Value(paramInfo.Name));
                  }
      
                  return parameters;
              }
          }
      }
      

    Step 7 – Edit the Program.cs

    1. Update Program.cs as follows:
      using Rhyous.SimpleArgs;
      using System;
      
      namespace Rhyous.AmazonS3BucketManager
      {
          class Program
          {
              static void Main(string[] args)
              {
                  new ArgsManager&amp;lt;ArgsHandler&amp;gt;().Start(args);
              }
      
              internal static void OnArgumentsHandled()
              {
                  var bucketName = Args.Value(&amp;quot;Bucket&amp;quot;);
                  var task = BucketManager.CreateBucket(bucketName);
                  task.Wait();
              }
          }
      }
      

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

    Next: Deleting a key pair
    Return to: Managing Amazon AWS with C#


    Understanding async and await, Task.WaitAll, Task.Run, and parallelism: part 2

    So another issue with async, await came up. I was using Task.WaitAll to make it parallel, but it wasn’t parallel.

    var task1 = Method1();
    var task2 = Method2();
    Task.WhenAll(task1, task2)
    

    Now Method1() and Method2() had some code that took some time. Now my code was making some large web service calls, but we can demonstrate this more easily just by sleeping for 10 seconds.

    public async Task Method1()
    {
        Thread.Sleep(10000); // Ten seconds
    }
    
    public async Task Method2()
    {
        await Task.Delay(10000); // 10 second task
    }
    
    

    I expected these to run in parallel, but they didn’t. Let me explain why.

    When calling a method that returns task, it doesn’t actually return until the first await. Notice Method1() doesn’t have an await. So Method1() will run synchronously until the first await.

    Method2(), however, is pretty much the same, but does have an await. So it returns immediately.

    Getting parallelism is easy. Wrap the method in Task.Run().

    public async Task Method1()
    {
    Task.Run(Thread.Sleep, 10000)); // Ten seconds
    }


    Amazon S3 Bucket Management with C#: Part 10 – Uploading all files in a directory recursively to an S3 Bucket

    Before getting started

    Skill Level: Intermediate

    Assumptions:

    1. You already gone through Parts 1-9 of Managing Amazon AWS with C#.

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

    • Rhyous.SimpleArgs
    • Single Responsibility Principle (S in S.O.L.I.D.)
    • async, await, parallelism
    • 10/100 rule

    Doing things by convention.

    Step 1 – Add a method to get the list of files in a local directory

    This isn’t the focus of our post, however, in order to upload all files in a directory recursively, we have to be able to list them. We are going to create a method that is 10 lines of code. The method has one single repsponsibility, to return all files in a directory recursively. It is not the responsibility of BucketManager.cs to do this. Hence we need a new class that has this responsibility.

    Another reason to move this method to its own file is that this method is itself 10 lines of code. While you can have methods longer than ten lines, more than ten lines is usually the first sign that the Single Responsibility principal is broken. Most beginning developers have a hard time seeing the many ways a method may be breaking the single responsibility principle. So a much easier rule, is the 10/100 rule. In the 10/100 rule, a method can only have 10 lines. This rule is pretty soft. Do brackets count? It doesn’t matter. What matters is that the 10 line mark, with or without brackets, is where you start looking at refactoring the method by splitting it into two or more smaller and simpler methods. This is a a Keep It Super Simple (K.I.S.S.) rule.

    1. Add the following utility class: FileUtils.cs.
      using System.Collections.Generic;
      using System.IO;
      using System.Linq;
      using System.Threading.Tasks;
      
      namespace Rhyous.AmazonS3BucketManager
      {
          public static class FileUtils
          {
              public static async Task&amp;lt;List&amp;lt;string&amp;gt;&amp;gt; GetFiles(string directory, bool recursive)
              {
                  var files = Directory.GetFiles(directory).ToList();
                  if (!recursive)
                      return files;
                  var dirs = Directory.GetDirectories(directory);
                  var tasks = dirs.Select(d =&amp;gt; GetFiles(d, recursive)).ToList();
                  while (tasks.Any())
                  {
                      var task = await Task.WhenAny(tasks);
                      files.AddRange(task.Result);
                      tasks.Remove(task);
                  }
                  return files;
              }
          }
      }
      

    Notice: The above class will get all files and directories, recursively. It will do it in parallel. Parallelism likely isn’t needed most of the time. Any non-parallel code that could list files and directories recursively would work. But if you were going to sync directories with tens of thousands of files each, parallelism might be a huge benefit.

    Step 2 – Add an UploadFiles method to BucketManager.cs

    1. Edit file called BucketManager.cs.
    2. Enter this new method:
              public static async Task UploadFiles(TransferUtility transferUtility, string bucketName, string directory)
              {
                  var files = await FileUtils.GetFiles(directory, true);
                  var directoryName = Path.GetFileName(directory); // This is not a typo. GetFileName is correct.            
                  var tasks = files.Select(f =&amp;gt; UploadFile(transferUtility, bucketName, f, f.Substring(f.IndexOf(directoryName)).Replace('\\', '/')));
                  await Task.WhenAll(tasks);
              }
      

    Notice 1: We follow the “Don’t Repeat Yourself (DRY) principle by having UploadFiles() forward each file to the singular UploadFile().
    Notice 2: We don’t use the await keyword when we redirect each file UploadFile. Instead we capture the returned Task objects and then we will await the completion of each of them.

    Step 3 – Update the Action Argument

    We should be very good at this by now. We 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&amp;lt;string&amp;gt;
                          {
                              "CreateBucket",
                              "CreateBucketDirectory",
                              "CreateTextFile",
                              "DeleteBucket",
                              "DeleteBucketDirectory",
                              "ListFiles",
                              "UploadFile",
                              "UploadFiles"
                          },
                          ...
      

    Note: There are enough of these now that I alphabetized them.

    Step 4 – Delete the Parameter dictionary

    In Part 4, we created a method to pass different parameters to different methods.We took note in Part 8 and Part 9 that we now have more exceptions than we have commonalities. It is time to refactor this.

    Another reason to refactor this is because the OnArgumentsHandled method is seriously breaking the 10/100 rule.

    Let’s start by deleting what we have.

    1. Delete the Dictionary line from Program.cs.
              static Dictionary&amp;lt;string, object[]&amp;gt; CustomParameters = new Dictionary&amp;lt;string, object[]&amp;gt;();
      
    2. Delete the section where we populated the dictionary.
                  // Use the Custom or Common pattern
                  CustomParameters.Add("CreateBucketDirectory", new object[] { s3client, bucketName, Args.Value("Directory") });
                  CustomParameters.Add("CreateTextFile", new object[] { s3client, bucketName, Args.Value("Filename"), Args.Value("Text") });
                  CustomParameters.Add("DeleteBucketDirectory", new object[] { s3client, bucketName, Args.Value("Directory") });
                  CustomParameters.Add("DeleteFile", new object[] { transferUtility, bucketName, Args.Value("Filename") });
                  CustomParameters.Add("UploadFile", new object[] { transferUtility, bucketName, Args.Value("File"), Args.Value("RemoteDirectory") });
      

    Step 5 – Implement parameters by convention

    To refactor the parameter passing, To refactor this, we are going use a convention.

    A convention is some arbitrary rule that when followed makes the code work. You have to be very careful when using conventions because they are usually not obvious. Because they are not obvious, the first rule of using a convention is this: Conventions must be documented.

    The convention is this: Make the Argument names match the method parameters. Argument names are not case sensitive, so we don’t have to worry about case. Just name.

    There are two exceptions to this convention. AmazonsS3Client and TransferUtility. We will handle those exceptions statically in code.

    Now, let’s implement our convention.

    • For each Argument, make sure the associated parameter is the same name.
      • Change bucketName to bucket in all methods.
      • Change file to filename in the DeleteFile method.
      • Change UploadLocation to RemoteDirectory in the UploadFile method.
      • Change directory to LocalDirectory in the UploadFiles method.
    • Create the following MethodInfoExtension.cs.
      using Amazon;
      using Amazon.S3;
      using Amazon.S3.Transfer;
      using Rhyous.SimpleArgs;
      using System;
      using System.Collections.Generic;
      using System.Configuration;
      using System.Reflection;
      
      namespace Rhyous.AmazonS3BucketManager
      {
          public static class MethodInfoExtensions
          {
              public static List&amp;lt;object&amp;gt; DynamicallyGenerateParameters(this MethodInfo mi)
              {
                  var parameterInfoArray = mi.GetParameters();
                  var parameters = new List&amp;lt;object&amp;gt;();
                  var region = RegionEndpoint.GetBySystemName(ConfigurationManager.AppSettings["AWSRegion"]);
                  foreach (var paramInfo in parameterInfoArray)
                  {
                      if (paramInfo.ParameterType == typeof(AmazonS3Client) || paramInfo.ParameterType == typeof(TransferUtility))
                          parameters.Add(Activator.CreateInstance(paramInfo.ParameterType, region));
                      if (paramInfo.ParameterType == typeof(string))
                          parameters.Add(Args.Value(paramInfo.Name));
                  }
      
                  return parameters;
              }
          }
      }
      

      Notice this class will dynamically query the parameters. AmazonS3Client and TransferUtility are exceptions. The rest of the parameters are created using a convention and pulled from Argument values.

    • Update Program.cs to use this new extension method.
              internal static void OnArgumentsHandled()
              {
                  var action = Args.Value("Action");
                  var flags = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy;
                  MethodInfo mi = typeof(BucketManager).GetMethod(action, flags);
                  List&amp;lt;object&amp;gt; parameters = mi.DynamicallyGenerateParameters();
                  var task = mi.Invoke(null, parameters.ToArray()) as Task;
                  task.Wait();
              }   
      

     

    Notice: Look how simple Program.OnArgumentsHandled method has become. By using this convention, and by moving the parameter creation to an extension method, we are down to six lines. The total size for the Program.cs class is 25 lines, including spaces.

    You can now move a directory to an Amazon S3 bucket using C#.

    <h3>Design Pattern: Facade</h3>

    Yes, we have just implement the popular Facade design pattern.

    Our project, and most specifically BucketManger.cs, represent an entire system: Amazon S3. When code is written to represent an entire system or substem, that code is called a Facade.

    Go to: Rhyous.AmazonS3BucketManager on GitHub to see the full example project from this 10 part tutorial.

    Return to: Managing Amazon AWS with C#


    Amazon S3 Bucket Management with C#: Part 9 – Uploading a file with its path to a Bucket

    Before getting started

    Skill Level: Beginner

    Assumptions:

    1. You already gone through Parts 1-8 of Managing Amazon AWS with C#.

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

    • Rhyous.SimpleArgs

    Step 1 – Alter the existing UploadFile method in BucketManager.cs

    We need the UploadFile method to take in a parameter that specifies the remote directory, which is the directory path on the S3 bucket. However, if no directory is specified, the key should simply be the file name.

    1. Edit file called BucketManager.cs.
    2. Enter this new method:
      Note: We are in luck, the TransferUtility object has an overload that takes in the key.

              public static async Task UploadFile(TransferUtility transferUtility, string bucketName, string file, string uploadLocation = null)
              {
                  var key = Path.GetFileName(file);
                  if (!string.IsNullOrWhiteSpace(uploadLocation))
                  {
                      uploadLocation = uploadLocation.EndsWith("/") ? uploadLocation : uploadLocation + "/";
                      key = uploadLocation + key;
                  }
                  await Task.Run(() => transferUtility.Upload(file, bucketName, key));
              }
      

    Note: This method is already added to the Action Argument, so we don’t need to update it.

    Step 2 – Add a RemoteDirectory Argument

    If we are going to upload a file to a specific location, we should know what that specific location is. So add an Argument for it.

    1. Add the following argument to ArgsHandler.cs.
                      ...
                      new Argument
                      {
                          Name = "RemoteDirectory",
                          ShortName = "rd",
                          Description = "The remote directory on the S3 Bucket.",
                          Example = "{name}=My/Remote/Directory",
                          Action = (value) =>
                          {
                              Console.WriteLine(value);
                          }
                      }
                      ...
      

    Step 4 – Update the parameter array passed to UploadFile

    We’ve already create a custom parameter array for the UploadFile action. We simply need to add a method for it

                // Use the Custom or Common pattern
                CustomParameters.Add("CreateBucketDirectory", new object[] { s3client, bucketName, Args.Value("Directory") });
                CustomParameters.Add("CreateTextFile", new object[] { s3client, bucketName, Args.Value("Filename"), Args.Value("Text") });
                CustomParameters.Add("DeleteBucketDirectory", new object[] { s3client, bucketName, Args.Value("Directory") });
                CustomParameters.Add("DeleteFile", new object[] { transferUtility, bucketName, Args.Value("Filename") });
                CustomParameters.Add("UploadFile", new object[] { transferUtility, bucketName, Args.Value("File"), Args.Value("RemoteDirectory") });
    

    Note: There are enough of these now that I alphabetized them.

    You can now specify the remote directory when uploading a file.

    Go to: Part 10 – Uploading all files in a directory recursively to an S3 Bucket

    Return to: Managing Amazon AWS with C#


    Amazon S3 Bucket Management with C#: Part 8 – Deleting a file in a Bucket

    Before getting started

    Skill Level: Beginner

    Assumptions:

    1. You already gone through Parts 1-7 of Managing Amazon AWS with C#.

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

    • Rhyous.SimpleArgs
    • Don’t Repeat Yourself (DRY) Principal

    Step 1 – Add a DeleteFile method to BucketManager.cs

    1. Edit file called BucketManager.cs.
    2. Enter this new method:
              public static async Task CreateTextFile(AmazonS3Client client, string bucketName, string filename, string text)
              {
                  var dirRequest = new PutObjectRequest
                  {
                      BucketName = bucketName,
                      Key = filename,
                      InputStream = text.ToStream()
                  };
                  await client.PutObjectAsync(dirRequest);
                  Console.WriteLine($"Created text file in S3 bucket: {bucketName}/{filename}");
              }
      

    Notice: The code is almost identical to that of deleting a directory, with only one exception. We aren’t ending with a /. We really should not have duplicate code. So lets fix this in the next step.

    Step 2 – Solve the Repetitive Code

    It is best practice to avoid having duplicate code. This is often called the “Don’t Repeat Yourself” principal. So let’s update the DeleteBucketDirectory code to forward to the DeleteFile code.

    1. Update the DeleteDirectory method so that both methods share code.
             public static async Task DeleteBucketDirectory(AmazonS3Client client, string bucketName, string directory)
              {
                  if (!directory.EndsWith("/"))
                      directory = directory += "/";
                  await DeleteFile(client, bucketName, directory);
              }
      

    Now the delete directory code is no longer repetitive. A directory is the same as a file, just with a slash. So the Delete directory correctly makes sure that the directory name ends with a slash, then forwards the call to delete file.

    Step 3 – Update the Action Argument

    We should be very good at this by now. We 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",
                              "UploadFile",
                              "CreateBucketDirectory",
                              "DeleteBucketDirectory",
                              "CreateTextFile",
                              "DeleteFile"
                          },
                          ...
      

    Note: There are no additional Arguments to add. To delete a file, we need the bucket name and a file name, which we already have Arguments for.

    Step 4 – Fix the parameter mismatch problem

    In Part 4, we created a method to pass different parameters to different methods. Let’s use that to pass in the correct parameters.

    However, take note that we now have more exceptions than we had commonalities. This suggests that it is about time to refactor this code. For now, we will leave it.

                // Use the Custom or Common pattern
                CustomParameters.Add("UploadFile", new object[] { transferUtility, bucketName, Args.Value("File") });
                CustomParameters.Add("CreateBucketDirectory", new object[] { s3client, bucketName, Args.Value("Directory") });
                CustomParameters.Add("DeleteBucketDirectory", new object[] { s3client, bucketName, Args.Value("Directory") });
                CustomParameters.Add("CreateTextFile", new object[] { s3client, bucketName, Args.Value("Filename"), Args.Value("Text") });
    
    

    You can now add a text file to an Amazon S3 bucket using C#.

    Homework: There is some repetitiveness between CreateFolder and DeleteFolder. What is it? (Hint: Directories end with a slash.)

    Go to: Part 9 – Uploading a file with its path to a Bucket

    Return to: Managing Amazon AWS with C#


    Amazon S3 Bucket Management with C#: Part 7 – Creating a text file in a Bucket

    Before getting started

    Skill Level: Beginner

    Assumptions:

    1. You already gone through Parts 1-6 of Managing Amazon AWS with C#.

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

    • Rhyous.SimpleArgs
    • Rhyous.StringLibrary

    Step 1 – Add NuGet Package

    1. Right-click on your project and choose Management NuGet Packages.
    2. Search for Rhyous.StringLibrary.
      This is a simple library for string extensions methods and more. String code that is not in .Net by default, yet the methods have proven over time to be commonly used.
    3. Install the Rhyous.StringLibrary NuGet package.

    Step 2 – Add a CreateTextFile method to BucketManager.cs

    1. Edit file called BucketManager.cs.
    2. Enter this new method:
              public static async Task CreateTextFile(AmazonS3Client client, string bucketName, string filename, string text)
              {
                  var dirRequest = new PutObjectRequest
                  {
                      BucketName = bucketName,
                      Key = filename,
                      InputStream = text.ToStream()
                  };
                  await client.PutObjectAsync(dirRequest);
                  Console.WriteLine($"Created text file in S3 bucket: {bucketName}/{filename}");
              }
      

    Notice: The code is almost identical to that of creating a directory, with two exceptions. We aren’t ending with a /. And instead of assigning zero bytes to InputStream, we assigned text.ToStream(). Rhyous.StringLibrary provides us the ToStream() extension method.

    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&amp;amp;lt;string&amp;amp;gt;
                          {
                              "CreateBucket",
                              "DeleteBucket",
                              "ListFiles",
                              "UploadFile",
                              "CreateBucketDirectory",
                              "DeleteBucketDirectory",
                              "CreateTextFile",
                          },
                          ...
      

    Step 3 – Add FileName and Text Arguments

    If we are going to create a text file, we need to know the file name and the text to insert.

    1. Edit the ArgsHandler.cs file to define an Action argument.
                      ...
                      new Argument
                      {
                          Name = "FileName",
                          ShortName = "N",
                          Description = "The name of text a file to create.",
                          Example = "{name}=MyTextfile.txt",
                          Action = (value) =>
                          {
                              Console.WriteLine(value);
                          }
                      },
                      new Argument
                      {
                          Name = "Text",
                          ShortName = "T",
                          Description = "The text to put in a text file.",
                          Example = "{name}=\"This is some text!\"",
                          Action = (value) =>
                          {
                              Console.WriteLine(value);
                          }
                      }
                      ...
      

    Step 4 – Fix the parameter mismatch problem

    In Part 4, we created a method to pass different parameters to different methods. Let’s use that to pass in the correct parameters.

    However, take note that we now have more exceptions than we had commonalities. This suggests that it is about time to refactor this code. For now, we will leave it.

                // Use the Custom or Common pattern
                CustomParameters.Add("UploadFile", new object[] { transferUtility, bucketName, Args.Value("File") });
                CustomParameters.Add("CreateBucketDirectory", new object[] { s3client, bucketName, Args.Value("Directory") });
                CustomParameters.Add("DeleteBucketDirectory", new object[] { s3client, bucketName, Args.Value("Directory") });
                CustomParameters.Add("CreateTextFile", new object[] { s3client, bucketName, Args.Value("Filename"), Args.Value("Text") });
    
    

    You can now add a text file to an Amazon S3 bucket using C#.

    Go to: Deleting a file in a Bucket

    Return to: Managing Amazon AWS with C#


    Amazon S3 Bucket Management with C#: Part 6 – Deleting a Directory in a Bucket

    Before getting started

    Skill Level: Beginner

    Assumptions:

    1. You already gone through Parts 1-5 of Managing Amazon AWS with C#.

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

    • Rhyous.SimpleArgs

    Step 1 – Add a DeleteBucketDirectory method to BucketManager.cs

    1. Edit file called BucketManager.cs.
    2. Enter this new method:
              public static async Task DeleteBucketDirectory(AmazonS3Client client, string bucketName, string directory)
              {
                  var dirRequest = new DeleteObjectRequest
                  {
                      BucketName = bucketName,
                      Key = directory + "/"
                  };
                  await client.DeleteObjectAsync(dirRequest);
                  Console.WriteLine($"Created S3 bucket folder: {bucketName}/{directory}/");
              }
      

    Note: Amazon S3 uses objects with a key ending in a / as a directory, so we have to call DeleteObjectAsync.

    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&amp;lt;string&amp;gt;
                          {
                              "CreateBucket",
                              "DeleteBucket",
                              "ListFiles",
                              "UploadFile",
                              "CreateBucketDirectory",
                              "DeleteBucketDirectory"
                          },
                          ...
      

    Step 3 – Fix the parameter mismatch problem

    In Part 4, we created a method to pass different parameters to different methods. Let’s use that to pass in the correct parameters.

                // Use the Custom or Common pattern
                CustomParameters.Add("UploadFile", new object[] { transferUtility, bucketName, Args.Value("File") });
                CustomParameters.Add("CreateBucketDirectory", new object[] { s3client, bucketName, Args.Value("Directory") });
                CustomParameters.Add("DeleteBucketDirectory", new object[] { s3client, bucketName, Args.Value("Directory") });
    

    You can now Delete a directory on S3, using C#.

    Go to:

    Return to: Managing Amazon AWS with C#


    Amazon S3 Bucket Management with C#: Part 5 – Creating a Directory in a Bucket

    Before getting started

    Skill Level: Beginner

    Assumptions:

    1. You already gone through Parts 1-4 of Managing Amazon AWS with C#.

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

    • Rhyous.SimpleArgs

    Step 1 – Add a CreateBucketDirectory method to BucketManager.cs

    1. Edit file called BucketManager.cs.
    2. Enter this new method:
              public static async Task CreateBucketDirectory(AmazonS3Client client, string bucketName, string directory)
              {
                  var dirRequest = new PutObjectRequest
                  {
                      BucketName = bucketName,
                      Key = directory + "/",
                      InputStream = new MemoryStream(new byte[0])
                  };
                  await client.PutObjectAsync(dirRequest);
                  Console.WriteLine($"Created S3 bucket folder: {bucketName}/{directory}/");
              }
      

    Note: Amazon S3 uses objects with a key ending in a / as a directory, so all we do is put a new empty object with a slash.

    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&amp;lt;string&amp;gt;
                          {
                              "CreateBucket",
                              "DeleteBucket",
                              "ListFiles",
                              "UploadFile",
                              "CreateBucketDirectory"
                          },
                          ...
      

    Step 3 – Fix the parameter mismatch problem

    In Part 4, we created a method to pass different parameters to different methods. Let’s use that to pass in the correct parameters.

                // Use the Custom or Common pattern
                CustomParameters.Add("UploadFile", new object[] { transferUtility, bucketName, Args.Value("File") });
                CustomParameters.Add("CreateBucketDirectory", new object[] { s3client, bucketName, Args.Value("Directory") });
    

    You can now add a directory on S3, using C#.

    Go to:

    Return to: Managing Amazon AWS with C#


    Amazon S3 Bucket Management with C#: Part 4 – Uploading a file to a Bucket

    Before getting started

    Skill Level: Beginner

    Assumptions:

    1. You already gone through Parts 1, 2, and 3 of Managing Amazon AWS with C#.

    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
    • Dependency Inject (D in S.O.L.I.D.)
    • Reflection
    • Custom or Common pattern

    Step 1 – Add an UploadFile method to BucketManager.cs

    1. Edit file called BucketManager.cs.
    2. Enter this new method:
              public static async Task UploadFile(TransferUtility transferUtility, string bucketName, string file)
              {
                  await Task.Run(() => transferUtility.Upload(file, bucketName));
              }
      

      Notice 1: This method has different parameters. We are going to have to fix Program.cs later. Up until now, all of our methods had the same parameters.

    3. Notice 2: The content of this method is an action we would like to run asynchronously, but it is not asynchronous. Task.Run is a static method that runs any method you call inside it asynchronously.

    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",
                                "UploadFile",
                            },
                            ...
        

    Step 3 – Add a File Argument

    If we are going to upload a file, we should know which file it is.

    1.                 new Argument
                      {
                          Name = "File",
                          ShortName = "f",
                          Description = "The file.",
                          Example = "{name}=c:\\some\file.txt",
                          CustomValidation = (value) =>
                          {
                              return File.Exists(value);
                          },
                          Action = (value) =>
                          {
                              Console.WriteLine(value);
                          }
                      }
      

      Notice: One of the cool features of SimpleArgs is the ability to declare custom validation. We don’t have to check elsewhere in our code if the file exists. We can use that to validate whether the File parameter is valid or not.

    Step 4 – Fix the parameter mismatch problem

    To be nice and S.O.L.I.D., our program now needs to determine the method’s dependencies and inject them into the method. It needs to do this dynamically at runtime.

    Well, with only one exception, it is most easy to use the Custom or Common pattern. The Custom or Common pattern simply means checking for a customization and if no customization exists, use the common implementation.

    To implement this, we could just use a simple IF condition. But just to demonstrate how a Dictionary can be used for the Custom or Common pattern, I will use it.

        class Program
        {
            static void Main(string[] args)
            {
                new ArgsManager<ArgsHandler>().Start(args);
            }
    
            static Dictionary<string, object[]> CustomParameters = new Dictionary<string, object[]>();
    
            internal static void OnArgumentsHandled()
            {
                var action = Args.Value("Action");
                var bucketName = Args.Value("Bucket");
                var file = Args.Value("File");
    
                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 s3client = new AmazonS3Client(region);
                var transferUtility = new TransferUtility(region);
    
                // Use the Custom or Common pattern
                CustomParameters.Add("UploadFile", new object[] { transferUtility, bucketName, file });
                object[] parameters;
                if (!CustomParameters.TryGetValue(action, out parameters))
                    parameters = new object[] { s3client, bucketName };
    
                var task = mi.Invoke(null, parameters) as Task;
                task.Wait();
            }
        }
    

    Notice in line 8 we create a dictionary. In line 24 we populate the dictionary with a customization. In Lines 26 and 27, we try to get a custom parameter array and if we don’t find it, we use the default one.

    Homework: What if every method had different parameters? What would you do?

    Go to: Part 5 – Creating a Directory in a Bucket

    Return to: Managing Amazon AWS with C#