Posts tagged ‘c#’

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<List<string>> 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 => 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 => 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<string>
                        {
                            "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<string, object[]> CustomParameters = new Dictionary<string, object[]>();
    
  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<object> DynamicallyGenerateParameters(this MethodInfo mi)
            {
                var parameterInfoArray = mi.GetParameters();
                var parameters = new List<object>();
                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<object> 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#

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#

    Handling a custom name space (xmlns) in an XML with Xml Serialization

    I have an xml that has a name space. This gave me to problems I had to resolve.

    1. How do I deserialize this xml with that name space value?
    2. How do I serialize the object to only have this one name space?

    Deserializing an Xml with a Name Space

    Below is an Xml file. Notice that the PrimaryNode has a name space that isn’t the default.

    <?xml version="1.0" encoding="utf-8"?>
    <PrimaryNode xmlns="http://some/random/namespace/example>
      <SecondaryNode>
        <TertiaryNode />
        <TertiaryNode />
      </SecondaryNode>
    </PrimaryNode>
    

    I had an object created as follows.

    using System;
    using System.Xml.Serialization;
    using System.Collections.ObjectModel;
    
    namespace XmlNameSpaceTest.Model
    {
        [Serializable()]
        public class PrimaryNode
        {
            private ObservableCollection<SecondaryNode> _Children;
    
            public PrimaryNode() { }
    
            [XmlElement("SecondaryNode")]
            public ObservableCollection<SecondaryNode> Children
            {
                get
                {
                    if (_Children == null)
                        _Children = new ObservableCollection<SecondaryNode>();
                    return _Children;
                }
            }
        }
    }
    

    I used this static class for the serialization

    using System;
    using System.IO;
    using System.Xml.Serialization;
    
    namespace XmlNameSpaceTest.Model
    {
        public class Serializer
        {
            #region Functions
            public static void SerializeToXML<T>(T t, String inFilename)
            {
                XmlSerializer serializer = new XmlSerializer(t.GetType());
                TextWriter textWriter = new StreamWriter(inFilename);
                serializer.Serialize(textWriter, t);
                textWriter.Close();
            }
    
            public static T DeserializeFromXML<T>(String inFilename)
            {
                XmlSerializer deserializer = new XmlSerializer(typeof(T));
                TextReader textReader = new StreamReader(inFilename);
                T retVal = (T)deserializer.Deserialize(textReader);
                textReader.Close();
                return retVal;
            }
            #endregion
        }
    }
    

    But it failed to deserialize because of the name space. It gave me this exception (which I trimmed to only show the important parts as it was very long).

    System.InvalidOperationException was unhandled
      Message=There is an error in XML document (2, 2).
      Source=System.Xml
      StackTrace:
           <-- snipped -->
      InnerException: System.InvalidOperationException
           Message=<PrimaryNode xmlns='http://some/random/namespace/example'> was not expected.
           Source=bgwwvhdx
           StackTrace:
                at Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationReaderPrimaryNode.Read7_PrimaryNode()
    

    The solution was simple. I had to add the XmlRoot tag above the class.

        [Serializable()]
        [XmlRoot(Namespace="http://some/random/namespace/example")]
        public class PrimaryNode
        {
            ... snipped...
        }
    

    This solved problem 1. The Xml now deserialized just fine.

    Serializing an Xml so it only contains a single Name Space

    When I serialized the object, there were two default name spaces, but neither were the custom name space that the Xml needed.

    <?xml version="1.0" encoding="utf-8"?>
    <PrimaryNode xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
      <SecondaryNode>
        <TertiaryNode />
        <TertiaryNode />
      </SecondaryNode>
    </PrimaryNode>
    

    Here is what I had to do.

    1. Add a XmlSerializerNamespaces property with only the one name space added.

        [Serializable()]
        [XmlRoot(Namespace="http://some/random/namespace/example")]
        public class PrimaryNode
        {
            ... snipped...
    
            private XmlSerializerNamespaces _Namespaces;
    
            public XmlSerializerNamespaces NameSpaces
            {
                get
                {
                    if (_Namespaces == null)
                    {
                        _Namespaces = new XmlSerializerNamespaces();
                        _Namespaces.Add("", "http://schemas.microsoft.com/wix/2006/wi");
                    }
                    return _Namespaces;
                }
            }
        }
    

    2. Then I had to change my Serializer class. I needed to overload the Serialize function by adding a version of it that accepts the XmlSerializerNamespaces object as the third parameter.

            public static void SerializeToXML<T>(T t, String inFilename, XmlSerializerNamespaces namespaces)
            {
                XmlSerializer serializer = new XmlSerializer(t.GetType());
                TextWriter textWriter = new StreamWriter(inFilename);
                serializer.Serialize(textWriter, t, namespaces);
                textWriter.Close();
            }
    

    3. Then, you’ve probably realized already, when serializing the object to Xml, we use this new function and pass it the PrimaryNode.NameSpaces as the third parameter.

    This solved the second problem.

    I hope this post helps you.

    A simple example of starting and stopping a service in windows using C++

    Hey all,

    Here is a simple example of starting and stopping a service in C++. Just pass the service name as a parameter and the service will switch states. If stopped, it will start. If started, it will stop.

    // StartStopService.cpp : Defines the entry point for the console application.
    //

    #include “stdafx.h”
    #include
    #include

    int _tmain(int argc, _TCHAR* argv[])
    {
    SC_HANDLE serviceDbHandle = OpenSCManager(NULL,NULL,SC_MANAGER_ALL_ACCESS);
    SC_HANDLE serviceHandle = OpenService(serviceDbHandle, argv[1], SC_MANAGER_ALL_ACCESS);

    SERVICE_STATUS_PROCESS status;
    DWORD bytesNeeded;
    QueryServiceStatusEx(serviceHandle, SC_STATUS_PROCESS_INFO,(LPBYTE) &status,sizeof(SERVICE_STATUS_PROCESS), &bytesNeeded);

    if (status.dwCurrentState == SERVICE_RUNNING)
    {// Stop it
    BOOL b = ControlService(serviceHandle, SERVICE_CONTROL_STOP, (LPSERVICE_STATUS) &status);
    if (b)
    {
    std::cout << "Service stopped." << std::endl; } else { std::cout << "Service failed to stop." << std::endl; } } else {// Start it BOOL b = StartService(serviceHandle, NULL, NULL); if (b) { std::cout << "Service started." << std::endl; } else { std::cout << "Service failed to start." << std::endl; } }CloseServiceHandle(serviceHandle); CloseServiceHandle(serviceDbHandle);return 0; } [/sourcecode]

    A simple example of creating or deleting a windows share using C++

    Hey all,

    Here is a simple example of creating or deleting a windows share in C++.

    // CreateShare.cpp : Defines the entry point for the console application.
    //

    #include “stdafx.h”
    #include
    #include
    #include “lm.h”

    int _tmain(int argc, _TCHAR* argv[])
    {
    // Create share
    if (0 == _tcscmp(argv[1], _T(“create”)))
    {
    SHARE_INFO_2 si =
    {
    L”ShareName”,
    STYPE_DISKTREE,
    L”Any nice comment or remark”,
    ACCESS_READ,
    DWORD(-1),
    0,
    L”C:\\Users\\jbarneck\\Desktop\\Share”,
    L””
    };

    DWORD parameterError = 0;
    DWORD status = NetShareAdd(NULL, 2, (BYTE *) &si, &parameterError);
    if (status != ERROR_SUCCESS)
    {
    std::cout << "Error: " << status << std::endl; if (status == ERROR_ACCESS_DENIED) // 5L in WinError.h { std::cout << "Access denied." << std::endl; } if (status == NERR_ServerNotStarted) // 2114 in LMErr.h { std::cout << "The Server service is stopped." << std::endl; } if (status == NERR_DuplicateShare) // 2118 in LMErr.h { std::cout << "The share already exists." << std::endl; } } // End program return 0; }// Delete share if (0 == _tcscmp(argv[1], _T("delete"))) { DWORD status = NetShareDel(NULL, L"ShareName", 0); if (status != ERROR_SUCCESS) { std::cout << "Could not delete share: " << status << std::endl; } // End program return 0; }} [/sourcecode]References: NetShareAdd - http://msdn.microsoft.com/en-us/library/bb525384%28VS.85%29.aspx NetShareDel - http://msdn.microsoft.com/en-us/library/bb525386%28v=VS.85%29.aspx

    How to limit or prevent characters in a TextBox in C#? Or How to create a NumberTextBox or DigitBox object?

    Let say you want to have a TextBox in which you only want to allow integers (0-9) or maybe you only want to allow strings, A-Za-z.

    Well, lets play around with this for a second and see what we can do.

    To get started do this:

    1. Create a new WPF Application project.
    2. In the designer, add a TextBox from the Toolbox.
    3. In the properties field for the TextBox click the icon that look like a lightening bolt to bring up events.
    4. Find the KeyDown event and double-click on it.

    Ok, so you should now have the following function:

            private void textBox1_KeyDown(object sender, KeyEventArgs e)
            {
            }
    

    The key that was pressed is accessible from the KeyEventArgs variable, e. Specifically e.Key.

    Key just happens to be an enum, which means they can basically be treated as integers.

    The key press can be ignored by telling setting e.Handled=true. This way it is already marked as handled and will not be added to the TextBox.

    Allow only number keys 0-9 in a TextBox

    Here is a simple function to allow only natural numbers or number keys 0-9 in a TextBox. Be aware that the keys may be different in other languages.

            private void textBox1_KeyDown(object sender, KeyEventArgs e)
            {
                if (e.Key < Key.D0 || e.Key > Key.D9)
                {
                    e.Handled = true;
                }
            }
    

    Wow, that was pretty simple, right?  WRONG! It is not that easy.

    You realize that there are two sets of numbers on a keyboard right? You have numbers in row above your QWERTY keys and you likely have a Number pad on the right of your keyboard as well.  That is not all either.

    What else did we forget, you might ask?  Well, of the seven requirements we need to handle, we only handled one.  For an application to be considered release quality or enterprise ready or stable, all seven of these should be handled.

    1. Numbers 1234567890 above QWERTY keys.
    2. Numpad numbers.
    3. You may want to allow pressing of the Delete, Backspace, and Tab keys.
    4. What about pasting?
    5. What about drag and drop?
    6. What about someone else calling your code and changing the text?
    7. Mnemonics should work.

    Requirements

    Here are the six requirements in clear statements.

    1. Allow pressing Numbers 1234567890 above QWERTY keys.
    2. Allow pressing Numpad numbers.
    3. Allow pressing of the Delete, Backspace, and Tab keys.
    4. Allow pasting so that only numbers in a string are added: A1B2C3 becomes 123.
    5. Allow drag and drop so that only numbers in a string are added: A1B2C3 becomes 123.
    6. Allow change in code at runtime so that only numbers in a string are added: A1B2C3 becomes 123.
    7. When another control has a mnemonic, such as Alt + S, pressing Alt + S, should property change the focus and place the cursor in the Alt + S control.

    Remembering lists like this is something that comes with experience.  If you thought of these on your own, good work.  If you didn’t think of them on your own, don’t worry, experience comes with time.

    So lets enhance this to handle each of these.

    Handling both Number keys and Numpad keys

            private void textBox1_KeyDown(object sender, KeyEventArgs e)
            {
                    e.Handled = !IsNumberKey(e.Key);
            }
    
            private bool IsNumberKey(Key inKey)
            {
                if (inKey < Key.D0 || inKey > Key.D9)
                {
                    if (inKey < Key.NumPad0 || inKey > Key.NumPad9)
                    {
                        return false;
                    }
                }
                return true;
            }
    

    All right, we now have two of the six requirements down.

    Handling Delete, Backspace, and Tab, and Mnemonics

    You can probably already guess how easy it will be to do something similar to handle these two keys.

            protected void OnKeyDown(object sender, KeyEventArgs e)
            {
                e.Handled = !IsNumberKey(e.Key) && !IsActionKey(e.Key);
            }
    
            private bool IsNumberKey(Key inKey)
            {
                if (inKey < Key.D0 || inKey > Key.D9)
                {
                    if (inKey < Key.NumPad0 || inKey > Key.NumPad9)
                    {
                        return false;
                    }
                }
                return true;
            }
    
            private bool IsActionKey(Key inKey)
            {
                return inKey == Key.Delete || inKey == Key.Back || inKey == Key.Tab || inKey == Key.Return || Keyboard.Modifiers.HasFlag(ModifierKeys.Alt);
            }
    

    Ok, now we have four of six requirements handled.

    Handling Paste (Ctrl + V) and Drag and Drop

    Yes, I can handle both at the same time with a new event TextChanged.

    This is setup so that if someone pastes both letters and number, only the numbers are pasted: A1B2C3 becomes 123.

    This event is not configured so we have to set it up.

    1. In the designer, click the TextBox.
    2. In the properties field for the TextBox click the icon that look like a lightening bolt to bring up events.
    3. Find the TextChanged event and double-click on it.

    You should now have this stub code for the event function.

    
            private void textBox1_TextChanged(object sender, TextChangedEventArgs e)
            {
            }
    

    Here is some easy code to make sure each character is actually a digit.

            protected void OnTextChanged(object sender, TextChangedEventArgs e)
            {
                base.Text = LeaveOnlyNumbers(Text);
            }
    
            private string LeaveOnlyNumbers(String inString)
            {
                String tmp = inString;
                foreach (char c in inString.ToCharArray())
                {
                    if (!IsDigit(c))
                    {
                        tmp = tmp.Replace(c.ToString(), "");
                    }
                }
                return tmp;
            }
    
            public bool IsDigit(char c)
            {
                return (c >= '0' && c <= '9');
            }
    

    Guess what else? This last function actual handles the first five requirements all by itself. But it is less efficient so we will leave the previous requirements as they are.

    Handling Direct Code Change

    Ok, so some somehow your TextBox is passed inside code during runtime a string that contains more than just numbers.  How are you going to handle it.

    This is setup so that if someone pastes both letters and number, only the numbers are pasted: A1B2C3 becomes 123.  Well, we need to run the same function as for Drag and Drop, so to not duplicate code, it is time to create a class or object.

    Creating a NumberTextBox object

    Now we need to make our code reusable. Lets create a class called NumberTextBox and it can do everything automagically.

    NumberTextBox

    using System;
    using System.Windows.Controls;
    using System.Windows.Input;
    
    namespace System.Windows.Controls
    {
        public class DigitBox : TextBox
        {
            #region Constructors
            /// <summary> 
            /// The default constructor
            /// </summary>
            public DigitBox()
            {
                TextChanged += new TextChangedEventHandler(OnTextChanged);
                KeyDown += new KeyEventHandler(OnKeyDown);
            }
            #endregion
    
            #region Properties
            new public String Text
            {
                get { return base.Text; }
                set
                {
                    base.Text = LeaveOnlyNumbers(value);
                }
            }
    
            #endregion
    
            #region Functions
            private bool IsNumberKey(Key inKey)
            {
                if (inKey < Key.D0 || inKey > Key.D9)
                {
                    if (inKey < Key.NumPad0 || inKey > Key.NumPad9)
                    {
                        return false;
                    }
                }
                return true;
            }
    
            private bool IsActionKey(Key inKey)
            {
                return inKey == Key.Delete || inKey == Key.Back || inKey == Key.Tab || inKey == Key.Return || Keyboard.Modifiers.HasFlag(ModifierKeys.Alt);
            }
    
            private string LeaveOnlyNumbers(String inString)
            {
                String tmp = inString;
                foreach (char c in inString.ToCharArray())
                {
                    if (!IsDigit(c))
                    {
                        tmp = tmp.Replace(c.ToString(), "");
                    }
                }
                return tmp;
            }
    
            public bool IsDigit(char c)
            {
                return (c >= '0' && c <= '9');
            }
            #endregion
    
            #region Event Functions
            protected void OnKeyDown(object sender, KeyEventArgs e)
            {
                e.Handled = !IsNumberKey(e.Key) && !IsActionKey(e.Key);
            }
    
            protected void OnTextChanged(object sender, TextChangedEventArgs e)
            {
                base.Text = LeaveOnlyNumbers(Text);
            }
            #endregion
        }
    }
    

    Now I can delete the events and functions from the Window1.xaml.cs file. I don’t have to add any code to the Window1.xaml.cs. Instead I need to reference my local namespace in the Window1.xaml and then change the TextBox to a local:NumberTextBox. Here is the XAML.

    <Window x:Class="TextBoxIntsOnly.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:TextBoxIntsOnly"
        Title="Window1" Height="300" Width="300">
        <Grid>
            <local:DigitBox Margin="87,27,71,0" VerticalAlignment="Top" x:Name="textBox1" />
            <Label Height="28" HorizontalAlignment="Left" Margin="9,25,0,0" Name="label1" VerticalAlignment="Top" Width="72">Integers:</Label>
            <TextBox Height="23" Margin="87,56,71,0" Name="textBox2" VerticalAlignment="Top" />
            <Label Height="28" HorizontalAlignment="Left" Margin="9,54,0,0" Name="label2" VerticalAlignment="Top" Width="72">Alphabet:</Label>
        </Grid>
    </Window>
    

    And now all seven requirements are met.

    Common Regular Expression Patterns for C#

    The following are code snippets for common regular expressions in C#.

    If you have a regular expression that you think is common or a correction/improvement to one of mine, please submit it.

    IP address pattern or expression

    The expression:

    ^[0-9]{1,3}\.){3}[0-9]{1,3}$
    

    In CSharp code:

    String theIpAddressPattern = @"^[0-9]{1,3}\.){3}[0-9]{1,3}$";
    

    Domain name pattern or expression

    The expression:

    ^[\-\w]+\.)+[a-zA-Z]{2,4}$
    

    In CSharp code:

    String theDoainNamePattern = @"^[\-\w]+\.)+[a-zA-Z]{2,4}$";
    

    Email address pattern or expression

    The expression:

    ^[\w!#$%&'*+\-/=?\^_`{|}~]+(\.[\w!#$%&'*+\-/=?\^_`{|}~]+)*@((([\-\w]+\.)+[a-zA-Z]{2,4}$)|(([0-9]{1,3}\.){3}[0-9]{1,3}))
    

    In CSharp code:

    String theEmailPattern = @"^[\w!#$%&'*+\-/=?\^_`{|}~]+(\.[\w!#$%&'*+\-/=?\^_`{|}~]+)*"
                                       + "@"
                                       + @"((([\-\w]+\.)+[a-zA-Z]{2,4})|(([0-9]{1,3}\.){3}[0-9]{1,3}))$";
    

    a

    The URL Pattern

    The expression:

    ((^http(s)*://(([\-\w]+\.)+[a-zA-Z]{2,4}.*)))$|(^ftp://([\w](:[\w]))*(([\-\w]+\.)+[a-zA-Z]{2,4}[/\w]*))$
    

    In CSharp code:

                String theURLPattern = @"((^http(s)*://(([\-\w]+\.)+[a-zA-Z]{2,4}.*)))$"
                                     + @"|(^ftp://([\w](:[\w]))*(([\-\w]+\.)+[a-zA-Z]{2,4}[/\w]*))$";