Month: June 2014

Zip Files with C#

Posted on Updated on

Introduction

We use .zip files all of the time and Windows users have come to expect this functionality to be present in many applications. There are numerous third-party products available that we can use to create .zip archives and these include WinZip, PkZip, WinRar and others. It is generally understood that we can create a .zip archive from numerous files or directories on a hard drive and that the resulting zip  archive is a single “file” with a .zip extension that contains the files and folders that we include. Zip files are good for storing and/or transferring files/folders for the following reasons:

  1. Since multiple files or folders can be “zipped” into a single file, we can simplify things by storing or transferring a single file and not many, many files. This is great for e-mail attachments, web-based file uploads/downloads and just simpler for both the packager and the recipient.
  2. Because zip files implement varying levels of compression, the size of the single zip file is less than the cumulative sizes of the files included in the zip file.

Creating a Zip File (Archive) via Windows 7

The Windows operating system provides .zip file functionality within My Computer. This is easily accomplished by selecting a folder or files, right-clicking, and selecting SendTo and Compressed (zipped) folder. See the illustration below.

Windows 7 - My Computer - Send To - Compressed Folder (.zip file)
Windows 7 – My Computer – Send To – Compressed Folder (.zip file)

I’m sure that we’ve all done this at one time or another, right? Sure we have. Now, let’s take look at how we can create and work with the built-in Windows zip file/archive functionality provided by Windows via C# and the .NET Framework.

Creating and Working with Zip Files in C#

In .NET 4.5, there are classes in the System.IO.Compression namespace that allow developers to quickly and easily create and work with zip files and archives. These classes are listed below:

  • ZipFile – provides static methods for creating, extracting, and opening zip files.
  • ZipArchive – represents a package of compressed files in a zip format. Allows us to work with a collection of compressed files in a robust manner.

So, let’s just get going with some sample code!

Creating a Zip File – System.IO.Compression.ZipFile Class

The ZipFile class provides static methods that allow developers to programmatically work with zip archives. To use these methods, we have to first reference the System.IO.Compression.FileSystem assembly. The following little walkthrough will allow us to use the methods of the ZipFile class to work with a simple zip archive. Let’s create a new C# Console Application and call it ZipFileConsoleApplication. Once we’ve done that, let’s Add a Reference to the System.IO.Compression.FileSystem assembly. See the Add Reference dialog below.

Add Reference Dialog - System.IO.Compression.FileSystem Assembly
Add Reference Dialog – System.IO.Compression.FileSystem Assembly

Now that we have this reference, we need the following using statement to the head of our Program.cs class:

using System.IO.Compression;

We are now ready to jump into our static Main() method and start coding!

You can select any folder you desire for your code, but I am going to work with my c:\Temp\ZipSample folder and I am going to create two small Microsoft Word documents which I will name SampleDocument1 and SampleDocument2. These are just sample files – you can use any that you wish.

So, let’s write some code that creates a zip archive from my c:\Temp\ZipSamples folder.

static void Main(string[] args)
{
    //provide the folder to be zipped
    string folderToZip = @"c:\Temp\ZipSample";

    //provide the path and name for the zip file to create
    string zipFile = @"c:\Temp\ZipSampleOutput\MyZippedDocuments.zip";

    //call the ZipFile.CreateFromDirectory() method
    ZipFile.CreateFromDirectory(folderToZip, zipFile);
}

After we press F5 and run our console application, we can check our output folder and we will find our new .zip file.

Zip File in our output folder
Zip File in our output folder

Works like a champ and even better, it’s really simple to implement.

The ZipFile.CreateFromDirectory() method has a few overloads that are worth mentioning. The first one that we looked at was the simplest of the three available. The second overload allows us to also specify a CompressionLevel value. This enum has three members and they are listed below:

  • NoCompression – this option specifies that no compression should be applied to the file.
  • Fastest – this option results in an operation that takes the least amount of time, but the total level of compression is less.
  • Optimal – this option results in an operation that provides the greatest level of compression, but in cases where there is a large number of files or subfolders, the compression operation will take more time.

This warrants some discussion. First, let’s recognize that when talking about compression, we are constantly forced to balance between the level of compression and the time required to produce the output. This is a tradeoff that is just inherent to the process. The CompressionLevel that you specify must be carefully chosen based on your scenario. An application that provides zip archive functionality could either allow the user to specify the compression level or it could dynamically specify it based on rules that you implement within the application. For example, your logic could assess the number of files, the disparity of file sizes (i.e. how many and how large), evaluate available disk space, and many other variables. Based on these “rules” that you would write, you could determine on the fly what compression strategy to use.

It is important to note here that the CompressionLevel enum does not give us control over how our files are compressed. There are extension methods in the ZipFileExtensions classes that allow us to do this if we so desire. I won’t go into too much detail about that here, but we do need to remember that our scenario should dictate which CompressionLevel we implement. Okay?

Now, armed with our knowledge of the additional overloads to the CreateFromDirectory() method and the existence of the CompressionLevel enum, let’s do three quick tests.

First, modify the last line of code (see sample code above) to utilize the second overload of the CreateFromDirectory() method, and let’s pick NoCompression for our CompressionLevel value. See below.

Test #1 – ZipFile.CreateFromDirectory() with CompressionLevel.NoCompression

static void Main(string[] args)
{
    //provide the folder to be zipped
    string folderToZip = @"c:\Temp\ZipSample";

    //provide the path and name for the zip file to create
    string zipFile = @"c:\Temp\ZipSampleOutput\MyZippedDocuments_NoCompression.zip";

    //call the ZipFile.CreateFromDirectory() method
    ZipFile.CreateFromDirectory(folderToZip, zipFile, CompressionLevel.NoCompression, false);

}

Press F5 and allow the application to run.

Test #2 – ZipFile.CreateFromDirectory() with CompressionLevel.Fastest

static void Main(string[] args)
{
    //provide the folder to be zipped
    string folderToZip = @"c:\Temp\ZipSample";

    //provide the path and name for the zip file to create
    string zipFile = @"c:\Temp\ZipSampleOutput\MyZippedDocuments_Fastest.zip";

    //call the ZipFile.CreateFromDirectory() method
    ZipFile.CreateFromDirectory(folderToZip, zipFile, CompressionLevel.Fastest, false);

}

Press F5 and allow the application to run.

Test #3 – ZipFile.CreateFromDirectory() with CompressionLevel.Optimal

static void Main(string[] args)
{
    //provide the folder to be zipped
    string folderToZip = @"c:\Temp\ZipSample";

    //provide the path and name for the zip file to create
    string zipFile = @"c:\Temp\ZipSampleOutput\MyZippedDocuments_Optimal.zip";

    //call the ZipFile.CreateFromDirectory() method
    ZipFile.CreateFromDirectory(folderToZip, zipFile, CompressionLevel.Optimal, false);

}

Press F5 and allow the application to run.

Now that we’ve used all three CompressionLevel values, let’s look at our output.

Zip File output - CompressionLevel
Zip File output – CompressionLevel

Since there were only two simple documents in my archive, the file sizes between Optimal and Fastest are the same. But look at the NoCompression zip file.

The key here is that as the number of files increases and the types of files becomes more diverse, you can expect to see greater and greater differences in file sizes when different CompressionLevel values used.

Extracting Files from a Zip File – System.IO.Compression.ZipFile.ExtractToDirectory() Method

In the same way that we easily created our zip file above, we can extract the contents of the zip archive to a folder that we specify. See the code below:

//specif the directory to which to extract the zip file
string extractFolder = @"c:\Temp\ZipSampleExtract\";

//call the ZipFile.ExtractToDirectory() method
ZipFile.ExtractToDirectory(zipFile, extractFolder);

After we press F5 and run the application, we now see the contents of our zip file extracted to the folder specified.

Contents of Zip File Extracted to specified folder
Contents of Zip File Extracted to specified folder

We’ve seen that with just a couple of simple method calls, we can create and extract a zip archive with C#.

The ZipFile class also has a couple of Load() method overloads which can be used to open a zip archive programmatically, but we are not going to discuss those in this article. Their implementation is very straightforward.

Working with Zip Archives – System.IO.Compression.ZipArchive Class

So what if we want to work with the individual entries within a zip archive? What if we want to read them and what if we want to add individual entries to a zip archive? What if we want to maybe selectively extract certain files and ignore others?

The ZipArchive class allows us to work with a package or collection of compressed files. To take this approach, you can do the following:

  • Get a single entry (file) from the package using the GetEntry() method.
  • Get an entire collection of entries (files) from the package using the Entries property.
  • Create a new entry in the package by invoking one of the CreateEntry() overloads.

Let’s look back to the zip file that we created in the ZipFile class example above. We will write some code that uses the ZipArchive class to get the entries in that archive.

First, we need to Add a Reference to the System.IO.Compression assembly. See below.

Adding a Reference to the System.IO.Compression namespace
Adding a Reference to the System.IO.Compression assembly

If we remove all the code in our Program class’s static Main() method and replace it with this:

//we will use one of the zip archives we created in our previous example
string zipFile = @"C:\Temp\ZipSampleOutput\MyZippedDocuments_Optimal.zip";

//now we define the path to which we want to extract the files
string extractPath = @"C:\Temp\ZipSampleExtract";

//instantiate a ZipArchive object via the ZipFile.OpenRead method
ZipArchive zipArchive = ZipFile.OpenRead(zipFile);

//get the entries in the zip archive
if (zipArchive.Entries != null && zipArchive.Entries.Count > 0)
{
    //iterate through the Entries collection and extract each file
    //to the extraction folder
    foreach (ZipArchiveEntry entry in zipArchive.Entries)
    {
        //extract the entry to the output folder
        entry.ExtractToFile(System.IO.Path.Combine(extractPath, entry.FullName));
    }
}

When we take a look in our extraction folder, we see the extracted files!

Extracted Files
Extracted Files

Using ZipArchive Class to Zip Multiple Files

So what if we have a scenario where we need to create a zip archive that contains files from various locations across a hard drive or networked file shares? Let’s just write some sample code that will illustrate how to zip files individually.

//provide the folder to be zipped
string folderToZip = @"c:\Temp\ZipSample";

//provide the path and name for the zip file to create
string zipFile = @"c:\Temp\ZipSampleOutput\MyZippedDocuments2.zip";

//create an instance of a ZipArchive object by calling the
//ZipFile.Open method with a ZipArchiveMode of Create
using (ZipArchive zipArchive = ZipFile.Open(zipFile, ZipArchiveMode.Create))
{
    //since we are using our original folder with a couple
    //of sample Word documents, let's get a reference to those files
    //we are using the DirectoryInfo.GetFiles() method instead of
    //a simple Directory.GetFiles() method call because the latter
    //returns a string array and the former returns a FileInfo array.
    //With a FileInfo object, we can parse out the full path and
    //the simple file name quickly and easily with no custom code
    DirectoryInfo di = new DirectoryInfo(folderToZip);
    FileInfo[] filesToArchive = di.GetFiles();

    //defensive code always checks for null BEFORE executing
    if (filesToArchive != null && filesToArchive.Length > 0)
    {
        //iterate the filesToArchive string array
        foreach (FileInfo fileToArchive in filesToArchive)
        {
            //the first argument is the full path and filename
            //the second argument (entryName) is just the name of the file
            //the third argument is the CompressionLevel enum value
            zipArchive.CreateEntryFromFile(fileToArchive.FullName, fileToArchive.Name, CompressionLevel.Optimal);
        }
    }
}

Let’s talk about this briefly. First, we established the path containing the files we want to zip. Though this example only zips files in a single directory, the files could actually be scattered about in many directories. The key is that we establish the files to be zipped at some point.

Next, you’ll notice that we wrapped our code in a using block. In a nutshell, implementing a using statement, we can ensure that any object that implements the IDisposable interface is properly used and disposed of by the .NET runtime. Any managed type that accesses or uses unmanaged resources such as objects with the file system or fonts (just a couple of examples) should implement the IDisposable interface and it should be instantiated and used within the context of a using statement. We won’t go into detail regarding the hows and whys of the using statement, but just be aware that in a situation such as the sample above, it is a good practice to consume the ZipArchive via a using statement. Make sense?

Our invocation of the ZipFileExtensions.CreateEntryFromFile() method utilized the overload that accepts the full path and file name, a string name for the entry, and a CompressionLevel enum value. To learn more about the this method, this MSDN resource provides lots of good information.

This completes our look at ways to create and work with zip files (archives) with C# and the .NET Framework 4.5. There are many good resources available to help you learn more, but the samples we’ve gone through should give you most if not all that you need to implement zip file functionality within your C# applications. I will cover more System.IO functionality in future posts and I will update existing posts routinely.

Happy coding!

Advertisement

System.Reflection – Generic Methods in C# Using Reflection

Posted on Updated on

Generics came on the scene in .NET 2.0 and have been a mainstay ever since! I have developed numerous application frameworks over the years and in every case I have made full use of generics. Very similar to templates in C++, generics allow dynamic type-based operations in a strongly-typed manner and can provide build-time validation. Generics allow us define classes and methods without committing to specific types while we are coding. I am not going to go into a discussion of generics in a general sense since this MSDN resource does a pretty good job of providing a description.

Generic Methods

For our example on to how invoke generic methods via Reflection, we are first going to create a few simple classes whose actual function is a bit nonsensical, but I believe the best way to explain intermediate to advanced concepts is to use simple examples, and thus this is the theme of a large part of my blog :).

  1. To start, using Visual Studio let’s create a new solution that we will call GenericMethodWalkthroughSolution.
  2. Next, let’s add two projects to our solution: a C# Class Library which we will call MyClassLibrary and a C# Console Application which we will call MyConsoleApplication.
  3. In the Console application, add a project reference to MyClassLibrary.
  4. Right-click the MyConsoleApplication project and selected “Set as Startup Project”.

When we are done with these four steps, our Solution Explorer should look like this:

Solution Explorer View with both projects and a project reference added
Solution Explorer View with both projects and a project reference added

Next, we will add four classes to our Class Library project. For fun, I am using classes that you may remember from your very first introduction to Object-Oriented Programming (OOP). Don’t laugh though as they are perfect for this discussion!

The classes are outlined below:

  • Mammal.cs – a class that will be the base class for the remaining two classes.
  • Human.cs – a class that will be instantiated to represent a human.
  • Dog.cs – a class that will be instantiated to represent a dog
  • Manager.cs – a class that will contain a generic method that will create an instance of a human or a dog, generically and a couple of methods that create either specifically.

We will just add a couple of properties to each of the first three then a single generic method to the fourth.

Since the Mammal class is our base class, let’s define a couple of properties that are common to all mammals. How about Weight and Age? Sounds good to me.

Our Mammal class looks like this:

public class Mammal
{
    public Int32 Weight
    {
        get;
        set;
    }

    public Int32 Age
    {
        get;
        set;
    }
}

Our Human class has a couple of properties and inherits Mammal:

public class Human : Mammal
{
    public string Name
    {
        get;
        set;
    }

    public String Occupation
    {
        get;
        set;
    }
}

Our Dog class has a couple of properties and inherits Mammal as well:

public class Dog : Mammal
{
    public String Breed
    {
        get;
        set;
    }

    public Boolean IsLongHaired
    {
        get;
        set;
    }
}

Finally, our Manager class has a single generic method called Create that is generic and accepts any type that inherits Mammal. See the simple code below:

public class Manager
{
    public T CreateMammal<T>()
        where T : Mammal, new()
    {
        return new T();
    }

    public Human CreateHuman()
    {
        return new Human();
    }

    public Dog CreateDog()
    {
        return new Dog();
    }
}

These methods are quite simple, so let’s not focus on their simplicity but focus instead on the concept we’re discussing. The CreateHuman() and CreateDog() methods simply return a new instance of each type. The Create method does the same, but generically and we will move ahead with our examples of how to call generic methods via Reflection.

Let’s return to our Console Application’s Program class and write some code in our static Main() method as shown below.

static void Main(string[] args)
{
    //first, let's just call the methods directly
    Manager manager = new Manager();

    //create a Human
    Human firstHuman = manager.CreateHuman();

    //create a Dog
    Dog firstDog = manager.CreateDog();

    //create a human generically
    Human secondHuman = manager.CreateMammal<Human>();

    //create a dog generically
    Dog secondDog = manager.CreateMammal<Dog>();
}

I included this initial code simply to illustrate normal invocation of each of the methods. Now we are going to invoke each of the methods via Reflection in the code block below.

static void Main(string[] args)
{
    Manager manager = new Manager();
    Type managerType = manager.GetType();

    //invoke the CreateHuman method
    Human human = (Human)managerType.GetMethod("CreateHuman").Invoke(manager, null);

    //invoke the CreateDog method
    Dog dog = (Dog)managerType.GetMethod("CreateDog").Invoke(manager, null);

    //invoke the CreateMammal method and create a Human
    MethodInfo createMammalMethod = managerType.GetMethod("CreateMammal");
    MethodInfo genericCreateMammalMethod = createMammalMethod.MakeGenericMethod(new Type[] { typeof(Human) });
    Human genericHuman = (Human)genericCreateMammalMethod.Invoke(manager, null);

    //invoke the CreateMammal method and create a Dog
    MethodInfo createMammalMethodDog = managerType.GetMethod("CreateMammal");
    MethodInfo genericCreateMammalMethodDog = createMammalMethodDog.MakeGenericMethod(new Type[] { typeof(Dog) });
    Dog genericDog = (Dog)genericCreateMammalMethodDog.Invoke(manager, null);
}

Okay, the last generic method invocations require some discussion. Let’s break out the first generic invocation of the CreateMammal method in the code block below and insert some commentary to provide an explanation.

//invoke the CreateMammal method and create a Human
//As with the other non-generic, Reflection-based invocations in the first two
//methods, we first have to get a reference to the MethodInfo object for the CreateMammal method.
MethodInfo createMammalMethod = managerType.GetMethod("CreateMammal");

//When invoking a generic method, we have to call the MakeGenericMethod method to tell the runtime
//what generic arguments are involved. In this case, we have one argument whose type is Human.
//Therefore, we create a new MethodInfo object that contains the substituted types for the generic argument(s).
MethodInfo genericCreateMammalMethod = createMammalMethod.MakeGenericMethod(new Type[] { typeof(Human) });

//Instead of using the first MethodInfo object we actually invoke the second (generic) one.
Human genericHuman = (Human)genericCreateMammalMethod.Invoke(manager, null);

So the important point here is this: to invoke a generic method via Reflection, you must call the MakeGenericMethod method of the MethodInfo object that holds a reference to the method prior to substitution of the generic arguments. Remember that a generic method may have more than one generic type, so therefore the MakeGenericMethod method accepts an object array that is the collection of generic types.

Now, if we don’t know the generic types at runtime like we did in this example, we can get them through the same MethodInfo object!  See the code block below:

MethodInfo method = managerType.GetMethod("CreateMammal");
if (method.IsGenericMethod == true)
{
    Type[] genericArguments = method.GetGenericArguments();
}

If we place a breakpoint on the fourth line of this block and add a Watch to the genericArguments object we can see for ourselves that the generic type is a type that has a BaseType of Mammal. See below:

Generic Argument Display in Watch Window
Generic Argument Display in Watch Window

 

Related Posts

System.Reflection – Working with the Assembly Class

System.Reflection – Working with the Type Class

System.Reflection – Working with the Type Class

Posted on Updated on

In a previous post, we took a brief look at the System.Reflection.Assembly class. In this post, we are going to move down a level and take a look at the Type class. Every object that resides within a .NET application is a specific Type.

Getting an Object’s Type (Type.GetType() method)

Let’s go ahead and dive in to some code samples that allow us to work with types a little. In the previous post we created a very simple Visual Studio solution that contained a C# Class Library and a C# Console Application. In this post we will add a new class to the console application then from the Program class’s static Main() method we will instantiate our new object. We will work with our new class and add methods to perform certain tasks related to getting Type information.

We will add a new class to the console application and we will name it ‘Person’. Since a person has qualities such as a name, age, height, weight, and so on, we will add a few properties to the class that we will then retrieve and work with via Reflection.

So when we add a few properties and a couple of nonsensical methods to the class, the full code of the Person class looks like this:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MyConsoleApplication
{
    public class Person
    {
        #region Public Properties

        public string Name
        {
            get;
            set;
        }

        public Int32 Age
        {
            get;
            set;
        }

        public Int32 Height
        {
            get;
            set;
        }

        public Int32 Weight
        {
            get;
            set;
        }

        #endregion Public Properties

        #region Public Methods

        public string SayHello()
        {
            return "Hello!";
        }

        public string Speak(string wordsToSpeak)
        {
            return wordsToSpeak;
        }

        #endregion Public Methods
    }
}

To create an instance of a Person, we add the following code to our console application’s Program class’s Main() method:

Person person = new Person();
Type personType = person.GetType();

Now, we will put a breakpoint on the second line shown above and hit F5 to run the console application. When we hit the breakpoint, we will right-click and set a Watch on the personType object. The results are shown below:

Watch window view of the personType object
Watch window view of the personType object

There are more properties of the Type object than I can display in the illustration above but this is more than enough to get us started. If you scan through the property list, you will that there is a lot of information about the type available.

Let’s talk about a few of the more important properties shown.

First, look at the Assembly property. You can see that the value describes the ConsoleApplication assembly, including its version, culture information, public key token (which is null because the assembly is not strongly-named).

Next, the AssemblyQualifiedName shows the same stuff, except the full namespace and type name of the Person object are prepended.

The BaseType property describes the type from which the Person class inherits. In this case, it inherits from System.Object. Had our class inherited another class, the BaseType would reflect that type, and its BaseType would reflect its base type and so on. Ultimately, the lowest level base class would have a BaseType of System.Object. Make sense?

All if the properties that start with Is* are Boolean values that indicate what their names describe.

So far so good? Great! Let’s move on to some things that are a little more interesting.

Getting the Properties within a Type (Type.GetProperties() method)

The Type class’s GetProperties() method allows us to retrieve all properties within a type. This method returns an array of PropertyInfo objects. The PropertyInfo type provides us access to the metadata of a property. Sound confusing? Let’s just dive into some code and clear it up!

Let’s return to our Main() method and add the following code:

Person person = new Person();
Type personType = person.GetType();

//get the PropertyInfo array for all properties
PropertyInfo[] properties = personType.GetProperties();

//create a StringBuilder to hold our formatted output
StringBuilder sb = new StringBuilder();

//iterate the properties and write output
foreach (PropertyInfo property in properties)
{
    sb.AppendLine("================================================================");
    sb.AppendLine(String.Format("Property Name: {0}", property.Name));
    sb.AppendLine("================================================================");

    sb.AppendLine(String.Format("Property Type Name: {0}", property.PropertyType.Name));
    sb.AppendLine(String.Format("Property Type FullName: {0}", property.PropertyType.FullName));

    sb.AppendLine(String.Format("Can we read the property?: {0}", property.CanRead.ToString()));
    sb.AppendLine(String.Format("Can we write the property?: {0}", property.CanWrite.ToString()));
}

string output = sb.ToString();

When we retrieve the string contents of our StringBuilder, we will see the following results:

================================================================
Property Name: Name
================================================================
Property Type Name: String
Property Type FullName: System.String
Can we read the property?: True
Can we write the property?: True
================================================================
Property Name: Age
================================================================
Property Type Name: Int32
Property Type FullName: System.Int32
Can we read the property?: True
Can we write the property?: True
================================================================
Property Name: Height
================================================================
Property Type Name: Int32
Property Type FullName: System.Int32
Can we read the property?: True
Can we write the property?: True
================================================================
Property Name: Weight
================================================================
Property Type Name: Int32
Property Type FullName: System.Int32
Can we read the property?: True
Can we write the property?: True

All of the properties that defined in our Person class are shown here with their data types (both short and full names) and with boolean values that indicate whether they are read-only or write-only properties. The presence of get and set accessors means that our properties are read and write properties. Pretty simple.

Getting and Setting Properties through Reflection

Being able to retrieve our properties is pretty cool, but we can also read values from and write values to our properties using Reflection. Now if we did just simply through the object itself, we could do it very simply like this:

person.Name = "John Nelson";
person.Age = 44;
person.Weight = 240;
person.Height = 70;

Now, to do it via Reflection is still simple, but slightly different. See the code below:

personType.GetProperty("Name").SetValue(person, "John Nelson", null);
personType.GetProperty("Age").SetValue(person, 44, null);
personType.GetProperty("Weight").SetValue(person, 240 , null);
personType.GetProperty("Height").SetValue(person, 70, null);

Okay, this warrants some discussion. To set property values via Reflection, you must use the Type.GetProperty() method, then invoke the PropertyInfo.SetValue() method. The default overload that we used accepts the object in which to set the property value, the value itself, and an object array, which in our example is null. The MSDN resource here explains the overloads of the SetValue() method in more detail.

Now that we’ve set the values using Reflection, let’s get them in the same way. We would do that as shown below:

string name = personType.GetProperty("Name").GetValue(person).ToString();
int age = (int)personType.GetProperty("Age").GetValue(person);
int weight = (int)personType.GetProperty("Weight").GetValue(person);
int height = (int)personType.GetProperty("Height").GetValue(person);

Getting property values is pretty straightforward and easy using Reflection.

Getting the Methods within a Type (Type.GetMethods() method)

In a similar manner to getting the properties within a type, getting and working with methods is pretty simple. For this sample, our methods do not contain generic arguments and I did this to keep the sample simple. We will cover invoking generically-typed methods in a later post.

The following code gets all the methods within our Person type and writes information about each method to a StringBuilder.

Person person = new Person();
Type personType = person.GetType();

//get the methods in the type
MethodInfo[] methods = personType.GetMethods();

//create a StringBuilder to hold our formatted output
StringBuilder sb = new StringBuilder();

//iterate the methods and write output
foreach (MethodInfo method in methods)
{
    sb.AppendLine("================================================================");
    sb.AppendLine(String.Format("Method Name: {0}", method.Name));
    sb.AppendLine("================================================================");

    sb.AppendLine(String.Format("Contains Generic Parameters: {0}", method.ContainsGenericParameters.ToString()));
    sb.AppendLine(String.Format("Is Abstract?: {0}", method.IsAbstract.ToString()));

    sb.AppendLine(String.Format("Is a Constructor?: {0}", method.IsConstructor.ToString()));
    sb.AppendLine(String.Format("Is it Generic?: {0}", method.IsGenericMethod.ToString()));

    sb.AppendLine(String.Format("Is it Private?: {0}", method.IsPrivate.ToString()));
    sb.AppendLine(String.Format("Is it Public?: {0}", method.IsPublic.ToString()));
    sb.AppendLine(String.Format("Is it Static?: {0}", method.IsStatic.ToString()));
    sb.AppendLine(String.Format("Is is Virtual?: {0}", method.IsVirtual.ToString()));

    //if the method is a void, the Return type will be null
    //otherwise, it will return a Type
    if (method.ReturnType != null && !String.IsNullOrEmpty(method.ReturnType.Name))
    {
        sb.AppendLine(String.Format("Return Type: {0}", method.ReturnType.Name.ToString()));
    }

    //there are more properties of the MethodInfo you could output...
}

string output = sb.ToString();

If we execute this code and retrieve the results of the StringBuilder.ToString() method, we will see the following output:

================================================================
Method Name: get_Name
================================================================
Contains Generic Parameters: False
Is Abstract?: False
Is a Constructor?: False
Is it Generic?: False
Is it Private?: False
Is it Public?: True
Is it Static?: False
Is it Virtual?: False
Return Type?: String
================================================================
Method Name: set_Name
================================================================
Contains Generic Parameters: False
Is Abstract?: False
Is a Constructor?: False
Is it Generic?: False
Is it Private?: False
Is it Public?: True
Is it Static?: False
Is it Virtual?: False
Return Type: Void
================================================================
Method Name: get_Age
================================================================
Contains Generic Parameters: False
Is Abstract?: False
Is a Constructor?: False
Is it Generic?: False
Is it Private?: False
Is it Public?: True
Is it Static?: False
Is it Virtual?: False
Return Type: Int32
================================================================
Method Name: set_Age
================================================================
Contains Generic Parameters: False
Is Abstract?: False
Is a Constructor?: False
Is it Generic?: False
Is it Private?: False
Is it Public?: True
Is it Static?: False
Is it Virtual?: False
Return Type: Void
================================================================
Method Name: get_Height
================================================================
Contains Generic Parameters: False
Is Abstract?: False
Is a Constructor?: False
Is it Generic?: False
Is it Private?: False
Is it Public?: True
Is it Static?: False
Is it Virtual?: False
Return Type: Int32
================================================================
Method Name: set_Height
================================================================
Contains Generic Parameters: False
Is Abstract?: False
Is a Constructor?: False
Is it Generic?: False
Is it Private?: False
Is it Public?: True
Is it Static?: False
Is it Virtual?: False
Return Type: Void
================================================================
Method Name: get_Weight
================================================================
Contains Generic Parameters: False
Is Abstract?: False
Is a Constructor?: False
Is it Generic?: False
Is it Private?: False
Is it Public?: True
Is it Static?: False
Is it Virtual?: False
Return Type: Int32
================================================================
Method Name: set_Weight
================================================================
Contains Generic Parameters: False
Is Abstract?: False
Is a Constructor?: False
Is it Generic?: False
Is it Private?: False
Is it Public?: True
Is it Static?: False
Is it Virtual?: False
Return Type: Void
================================================================
Method Name: SayHello
================================================================
Contains Generic Parameters: False
Is Abstract?: False
Is a Constructor?: False
Is it Generic?: False
Is it Private?: False
Is it Public?: True
Is it Static?: False
Is it Virtual?: False
Return Type: String
================================================================
Method Name: Speak
================================================================
Contains Generic Parameters: False
Is Abstract?: False
Is a Constructor?: False
Is it Generic?: False
Is it Private?: False
Is it Public?: True
Is it Static?: False
Is it Virtual?: False
Return Type: String
================================================================
Method Name: ToString
================================================================
Contains Generic Parameters: False
Is Abstract?: False
Is a Constructor?: False
Is it Generic?: False
Is it Private?: False
Is it Public?: True
Is it Static?: False
Is it Virtual?: True
Return Type: String
================================================================
Method Name: Equals
================================================================
Contains Generic Parameters: False
Is Abstract?: False
Is a Constructor?: False
Is it Generic?: False
Is it Private?: False
Is it Public?: True
Is it Static?: False
Is it Virtual?: True
Return Type: Boolean
================================================================
Method Name: GetHashCode
================================================================
Contains Generic Parameters: False
Is Abstract?: False
Is a Constructor?: False
Is it Generic?: False
Is it Private?: False
Is it Public?: True
Is it Static?: False
Is it Virtual?: True
Return Type?: Int32
================================================================
Method Name: GetType
================================================================
Contains Generic Parameters: False
Is Abstract?: False
Is a Constructor?: False
Is it Generic?: False
Is it Private?: False
Is it Public?: True
Is it Static?: False
Is it Virtual?: False
Return Type: Type

This is odd because we only defined two methods for our class, right? Well, it actually makes perfect sense because we retrieve ALL of the methods for the Person Type.

Beginning from the top of the results list, take a look at the methods whose names begin with “get_” and “set_”. It just so happens that properties are implemented in MSIL as methods! The get accessor is actually a method with a name that begins with “get_” and the set accessor is a method that begins with “set_”. So in reality, each of our properties created two (2) methods.

The ToString(), Equals(), GetHashCode(), and GetType() methods are actually inherited from the System.Object class which is the default base class of ALL objects in .NET.

So if we eliminate these methods, we are left with our two public methods, SayHello() and Speak().

Invoking a Method via Reflection

When dealing with non-generic methods, there are two basic ways to invoke them dynamically.

  1. The first approach is to get a reference to the MethodInfo object for the method to be called, then calling the MethodInfo.Invoke(object, object[]) method. With this approach, the first argument is the object within which the method is to be called. In our case the ‘person’ object. The second argument is an object array that accepts the arguments expected by the method in the order they are specified in the method.
  2. The second approach is to Type.InvokeMenber(methodName, Binder, object, object[]) method overload. I chose this overload of the InvokeMember method because it is the simplest and is perfect for illustrative purposes.

So let’s get going with some code to test this out! Let’s stick with our iterative approach to loop through the methods as we did above, but lets add some code that checks to see if the method name is “Speak” or “SayHello”, then invoke the method using Approach #1 first.

If we replace the code with the code listed below (see new lines 32-44):

//get the methods in the type
MethodInfo[] methods = personType.GetMethods();

//create a StringBuilder to hold our formatted output
StringBuilder sb = new StringBuilder();

//iterate the methods and write output
foreach (MethodInfo method in methods)
{
    sb.AppendLine("================================================================");
    sb.AppendLine(String.Format("Method Name: {0}", method.Name));
    sb.AppendLine("================================================================");

    sb.AppendLine(String.Format("Contains Generic Parameters: {0}", method.ContainsGenericParameters.ToString()));
    sb.AppendLine(String.Format("Is Abstract?: {0}", method.IsAbstract.ToString()));

    sb.AppendLine(String.Format("Is a Constructor?: {0}", method.IsConstructor.ToString()));
    sb.AppendLine(String.Format("Is it Generic?: {0}", method.IsGenericMethod.ToString()));

    sb.AppendLine(String.Format("Is it Private?: {0}", method.IsPrivate.ToString()));
    sb.AppendLine(String.Format("Is it Public?: {0}", method.IsPublic.ToString()));
    sb.AppendLine(String.Format("Is it Static?: {0}", method.IsStatic.ToString()));
    sb.AppendLine(String.Format("Is it Virtual?: {0}", method.IsVirtual.ToString()));

    //if the method is a void, the Return type will be null
    //otherwise, it will return a Type
    if (method.ReturnType != null && !String.IsNullOrEmpty(method.ReturnType.Name))
    {
        sb.AppendLine(String.Format("Is is Private?: {0}", method.ReturnType.Name.ToString()));
    }

    if (method.Name == "SayHello")
    {
        object result = method.Invoke(person, new object[] { });
        if (result != null)
        {
            sb.AppendLine(String.Format("***Calling the SayHello() method. The result of the method call is: {0}", result.ToString()));
        }
    }
    else if (method.Name == "Speak")
    {
        object result = method.Invoke(person, new object[] { "How are you today?" });
        sb.AppendLine(String.Format("***Calling the Speak() method. The result of the method call is: {0}", result.ToString()));
    }

    //there are more properties of the MethodInfo you could output...
}

string output = sb.ToString();

When we execute the new code the output for the Speak() and SayHello() methods now includes the results too:

================================================================
Method Name: SayHello
================================================================
Contains Generic Parameters: False
Is Abstract?: False
Is a Constructor?: False
Is it Generic?: False
Is it Private?: False
Is it Public?: True
Is it Static?: False
Is it Virtual?: False
Is it Private?: String
***Calling the SayHello() method. The result of the method call is: Hello!
================================================================
Method Name: Speak
================================================================
Contains Generic Parameters: False
Is Abstract?: False
Is a Constructor?: False
Is it Generic?: False
Is it Private?: False
Is it Public?: True
Is it Static?: False
Is it Virtual?: False
Is it Private?: String
***Calling the Speak() method. The result of the method call is: How are you today?

Pay attention to the lines in blue. We actually called those methods and added the results in a formatted way to our sample output StringBuilder. Pretty simple, right?

Now we take a slightly different approach and simply invoke the method via Reflection by name. This is very straightforward and can be done as shown below:

//invoke the SayHello method
object methodResult = personType.GetMethod("SayHello").Invoke(person, null);
if (methodResult != null)
{
    string thePersonSaid = methodResult.ToString();
}

//invoke the Speak method and pass the string argument in
object methodResult2 = personType.GetMethod("Speak").Invoke(person, new object[] { "I have spoken." });
if (methodResult2 != null)
{
    string thePersonThenSaid = methodResult2.ToString();
}

So now we have looked at a couple of ways to invoke methods via Reflection. One method accepts no arguments and the other accepted a string. What if we had a method that accepted two or more arguments?

Go back to our Person class and add the following method:

public string CallTheChildrenInForDinner(string firstChild, string secondChild)
{
    return String.Format("{0}, {1} it is time for dinner!", firstChild, secondChild);
}

We can invoke this method with the following code:

object methodResult3 = personType.GetMethod("CallTheChildrenInForDinner").Invoke(person, new object[] { "Bobby", "Gene" });
if (methodResult3 != null)
{
    string dinnerCall = methodResult3.ToString();
}

The result of this method call is: Bobby, Gene it is time for dinner!.

Getting the Interfaces Implemented by a Type

Within C#, a class can only inherit one base class (i.e. can only have one BaseType). A class can, however implement multiple interfaces. An interface looks much like a class, but only contains the definitions (signatures) of the public properties, methods, events, and indexers within the class that implements it. When discussing interfaces, we use the term “implements” for any class whose construction is governed by an interface. It is an accepted rule that interface names should generally begin with the letter “I”.

The Type class contains methods that allow us to retrieve interface implementation details for any given type. To take a look at this, let’s add an IPerson interface to our class library project. The code below is the full definition of the IPerson interface:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MyConsoleApplication
{
    public interface IPerson
    {
        #region Public Properties

        string Name { get; set; }       

        Int32 Age { get; set; }

        Int32 Height {get; set; }

        Int32 Weight { get; set; }        

        #endregion Public Properties

        #region Public Methods

        string SayHello();

        string Speak(string wordsToSpeak);

        string CallTheChildrenInForDinner(string firstChild, string secondChild);

        #endregion Public Methods
    }
}

Now that we’ve created our interface, let’s flip over to our Person class and implement the interface.

public class Person : IPerson

Now that we’ve done this, let’s modify the code in our Main() method to find the interfaces implemented by the Person type. See the code below:

Person person = new Person();
Type personType = person.GetType();
Type[] interfaces = personType.GetInterfaces();

If we place a breakpoint on the last line of the code above and add a Watch to the interfaces object, we will see the output displayed below:

Watch output for personType.GetInterfaces() method
Watch output for personType.GetInterfaces() method

This shows us that the IPerson interface is implemented by the Person type. If we expand the watch definition for the IPerson type we see all the properties for that that we saw for the Person class. The differences will be in the IsClass and IsInterface properties. Pretty simple.

System.Reflection – Working with the Assembly Class

Posted on Updated on

The objects within the System.Reflection namespace of the .NET Framework provide us a means to get assembly and type information at runtime. Reflection is useful in situations where you need to introspect assemblies and their types, either to gain information or to make use of their functionality dynamically without knowing its specifics in advance. Sounds pretty cool, right? Well it is!

This article will walk you through the basics of Reflection and the Assembly class and will be followed by additional articles that dive much more deeply into the objects within the System.Reflection namespace.

The System.Reflection.Assembly Class

The Assembly class is the primary class within the System.Reflection namespace. This will be our starting point as we take a look at Reflection.

First, we will create a simple Visual Studio solution that we will call ‘ReflectionWalkthroughSolution’.

In this solution, we will add a C# Class Library project which we will call ‘MyClassLibrary’.

Next, we will add a Console Application which we will call ‘MyConsoleApplication’. These names are nonsensical, but this solution is merely for demonstration purposes :).

So, after doing these things, our Solution Explorer should look like this:

.NET Reflection - Solution Explorer Getting Started

MyConsoleApplication is bolded because I have set it as the startup project. This will allow us to hit F5 to debug the app and be able to use the console application as our entry point.

Next, expand MyConsoleApplication and Add a Reference to the MyClassLibrary project. Once this is done we are ready to start writing a little code.

Assembly.GetExecutingAssembly() Method

The first thing we will look at is how to get a reference to the assembly that is currently executing. To do this we will add some simple code in the Main() method of our console application as shown below. After writing this code, place a breakpoint on the line, then hover the thisAssembly object and take a look at the properties of the assembly and their values.

static void Main(string[] args)
{
    //get the currently executing assembly
    Assembly thisAssembly = Assembly.GetExecutingAssembly();

}

With the code running, when the breakpoint is hit, we can right-click and Add a Watch to the thisAssembly object. When we hit F10 to step over the line, the Watch window is populated with information pertaining to the ‘thisAssembly’ object. See the illustration below.

Hint: Click the image to view it full-size.

.NET Reflection - Assembly.GetExecutingAssembly() Watch Window
Properties of the ‘thisAssembly’ Assembly object

 

Let’s discuss some of the properties that are shown in the Watch window above.

The CodeBase property returns the full path at which the assembly resides. In other words, the execution location for the assembly, including the name of the assembly.dll file itself.

The CustomAttributes property is an IEnumerable<CustomAttributeData> type that displays all of the custom attributes that exist at the assembly level. If you take a look you will see that many of these come directly from the AssemblyInfo.cs file that is found under the Properties folder of the project.

Assembly.CustomAttributes Illustration
The Assembly.CustomAttributes property expanded.

We will dive into custom attributes in a later post. Attributes can be applied to assemblies, classes, properties, methods, and more and through Reflection can be retrieved and read using the appropriate objects. These attributes exist at the assembly level. For now, just be aware that these attributes can be retrieved in this way.

The DefinedTypes property is an IEnumerable<TypeInfo> type that displays all of the types (classes) defined within the assembly. In our simple little console application we only have one defined type. See below.

The Assembly.DefinedTypes property expanded
The Assembly.DefinedTypes property expanded

Looking ahead to later posts, once you have a reference to an Assembly, knowing the types defined in the assembly allow you to dive into the assembly and then into each type as needed.

The FullName property is a string value that displays the full name of the assembly. This is important when discussing a topic such as strong naming.

The Assembly.FullName property
The Assembly.FullName property

There are other properties of the Assembly object that are noteworthy for this introduction. They are the GlobalAssemblyCache property which tells you whether or not the assembly is GAC’ed, the ImageRuntimeVersion which tells you the full version of the .NET Framework against which the assembly is compiled, and the MainfestModule which gives specific information about the assembly itself.

Getting Assemblies loaded in the Current AppDomain (AppDomain.Current.GetAssemblies() Method)

Now that we’ve taken a quick look at the properties of an Assembly and how to get a reference to the currently executing assembly, let’s branch out a little and retrieve all of the assemblies referenced and loaded into the current AppDomain. An AppDomain is an isolated environment in which an application executes. The AppDomain class belongs to the System namespace. To retrieve the AppDomain for the currently executing application, we will the AppDomain.Current property.

If we return to the Main() method of the Program class of our little console application and remove the code we wrote originally and replace it with this code:

static void Main(string[] args)
{
    Assembly thisAssembly = Assembly.GetExecutingAssembly();

    //Get assemblies loaded in the current AppDomain
    Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();

    //create a StringBuilder to hold assembly information
    StringBuilder sb = new StringBuilder();

    //iterate through the array and write info for each assembly
    foreach (Assembly assembly in assemblies)
    {
        //let's create some rudimentary formatted output to show property values
        //for each assembly
        sb.AppendLine("================================================================");
        sb.AppendLine(String.Format("Assembly: {0}", assembly.FullName));
        sb.AppendLine("================================================================");

        sb.AppendLine(String.Format("CodeBase: {0}", assembly.CodeBase));
        sb.AppendLine(String.Format("Location: {0}", assembly.Location));
        sb.AppendLine(String.Format("Number of Types: {0}", assembly.DefinedTypes.Count().ToString()));
        sb.AppendLine(String.Format("Number of Custom Attributes:  {0}", assembly.CustomAttributes.Count().ToString()));
        sb.AppendLine(String.Format(".NET Runtime Version: {0}", assembly.ImageRuntimeVersion));

        //you can add more stuff here to see more properties...
    }

    string output = sb.ToString();
}

When we view the formatted output from our StringBuilder, it should look something like this:

================================================================
Assembly: mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
================================================================
CodeBase: file:///C:/Windows/Microsoft.NET/Framework/v4.0.30319/mscorlib.dll
Location: C:\Windows\Microsoft.NET\Framework\v4.0.30319\mscorlib.dll
Number of Types: 3029
Number of Custom Attributes:  36
.NET Runtime Version: v4.0.30319
================================================================
Assembly: Microsoft.VisualStudio.HostingProcess.Utilities, Version=11.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
================================================================
CodeBase: file:///C:/Windows/assembly/GAC_MSIL/Microsoft.VisualStudio.HostingProcess.Utilities/11.0.0.0__b03f5f7f11d50a3a/Microsoft.VisualStudio.HostingProcess.Utilities.dll
Location: C:\Windows\assembly\GAC_MSIL\Microsoft.VisualStudio.HostingProcess.Utilities\11.0.0.0__b03f5f7f11d50a3a\Microsoft.VisualStudio.HostingProcess.Utilities.dll
Number of Types: 12
Number of Custom Attributes:  17
.NET Runtime Version: v2.0.50727
================================================================
Assembly: System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
================================================================
CodeBase: file:///C:/Windows/Microsoft.Net/assembly/GAC_MSIL/System.Windows.Forms/v4.0_4.0.0.0__b77a5c561934e089/System.Windows.Forms.dll
Location: C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System.Windows.Forms\v4.0_4.0.0.0__b77a5c561934e089\System.Windows.Forms.dll
Number of Types: 2273
Number of Custom Attributes:  27
.NET Runtime Version: v4.0.30319
================================================================
Assembly: System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
================================================================
CodeBase: file:///C:/Windows/Microsoft.Net/assembly/GAC_MSIL/System.Drawing/v4.0_4.0.0.0__b03f5f7f11d50a3a/System.Drawing.dll
Location: C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System.Drawing\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Drawing.dll
Number of Types: 303
Number of Custom Attributes:  25
.NET Runtime Version: v4.0.30319
================================================================
Assembly: System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
================================================================
CodeBase: file:///C:/Windows/Microsoft.Net/assembly/GAC_MSIL/System/v4.0_4.0.0.0__b77a5c561934e089/System.dll
Location: C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System\v4.0_4.0.0.0__b77a5c561934e089\System.dll
Number of Types: 2278
Number of Custom Attributes:  29
.NET Runtime Version: v4.0.30319
================================================================
Assembly: Microsoft.VisualStudio.HostingProcess.Utilities.Sync, Version=11.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
================================================================
CodeBase: file:///C:/Windows/assembly/GAC_MSIL/Microsoft.VisualStudio.HostingProcess.Utilities.Sync/11.0.0.0__b03f5f7f11d50a3a/Microsoft.VisualStudio.HostingProcess.Utilities.Sync.dll
Location: C:\Windows\assembly\GAC_MSIL\Microsoft.VisualStudio.HostingProcess.Utilities.Sync\11.0.0.0__b03f5f7f11d50a3a\Microsoft.VisualStudio.HostingProcess.Utilities.Sync.dll
Number of Types: 5
Number of Custom Attributes:  18
.NET Runtime Version: v2.0.50727
================================================================
Assembly: Microsoft.VisualStudio.Debugger.Runtime, Version=11.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
================================================================
CodeBase: file:///C:/Windows/assembly/GAC_MSIL/Microsoft.VisualStudio.Debugger.Runtime/11.0.0.0__b03f5f7f11d50a3a/Microsoft.VisualStudio.Debugger.Runtime.dll
Location: C:\Windows\assembly\GAC_MSIL\Microsoft.VisualStudio.Debugger.Runtime\11.0.0.0__b03f5f7f11d50a3a\Microsoft.VisualStudio.Debugger.Runtime.dll
Number of Types: 10
Number of Custom Attributes:  17
.NET Runtime Version: v2.0.50727
================================================================
Assembly: vshost32, Version=11.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
================================================================
CodeBase: file:///C:/Temp/ReflectionWalkthroughSolution/MyConsoleApplication/bin/Debug/MyConsoleApplication.vshost.exe
Location: C:\Temp\ReflectionWalkthroughSolution\MyConsoleApplication\bin\Debug\MyConsoleApplication.vshost.exe
Number of Types: 1
Number of Custom Attributes:  19
.NET Runtime Version: v4.0.30319
================================================================
Assembly: System.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
================================================================
CodeBase: file:///C:/Windows/Microsoft.Net/assembly/GAC_MSIL/System.Core/v4.0_4.0.0.0__b77a5c561934e089/System.Core.dll
Location: C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System.Core\v4.0_4.0.0.0__b77a5c561934e089\System.Core.dll
Number of Types: 929
Number of Custom Attributes:  25
.NET Runtime Version: v4.0.30319
================================================================
Assembly: System.Xml.Linq, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
================================================================
CodeBase: file:///C:/Windows/Microsoft.Net/assembly/GAC_MSIL/System.Xml.Linq/v4.0_4.0.0.0__b77a5c561934e089/System.Xml.Linq.dll
Location: C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System.Xml.Linq\v4.0_4.0.0.0__b77a5c561934e089\System.Xml.Linq.dll
Number of Types: 86
Number of Custom Attributes:  22
.NET Runtime Version: v4.0.30319
================================================================
Assembly: System.Data.DataSetExtensions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
================================================================
CodeBase: file:///C:/Windows/Microsoft.Net/assembly/GAC_MSIL/System.Data.DataSetExtensions/v4.0_4.0.0.0__b77a5c561934e089/System.Data.DataSetExtensions.dll
Location: C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System.Data.DataSetExtensions\v4.0_4.0.0.0__b77a5c561934e089\System.Data.DataSetExtensions.dll
Number of Types: 25
Number of Custom Attributes:  24
.NET Runtime Version: v4.0.30319
================================================================
Assembly: Microsoft.CSharp, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
================================================================
CodeBase: file:///C:/Windows/Microsoft.Net/assembly/GAC_MSIL/Microsoft.CSharp/v4.0_4.0.0.0__b03f5f7f11d50a3a/Microsoft.CSharp.dll
Location: C:\Windows\Microsoft.Net\assembly\GAC_MSIL\Microsoft.CSharp\v4.0_4.0.0.0__b03f5f7f11d50a3a\Microsoft.CSharp.dll
Number of Types: 316
Number of Custom Attributes:  23
.NET Runtime Version: v4.0.30319
================================================================
Assembly: System.Data, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
================================================================
CodeBase: file:///C:/Windows/Microsoft.Net/assembly/GAC_32/System.Data/v4.0_4.0.0.0__b77a5c561934e089/System.Data.dll
Location: C:\Windows\Microsoft.Net\assembly\GAC_32\System.Data\v4.0_4.0.0.0__b77a5c561934e089\System.Data.dll
Number of Types: 1049
Number of Custom Attributes:  21
.NET Runtime Version: v4.0.30319
================================================================
Assembly: System.Xml, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
================================================================
CodeBase: file:///C:/Windows/Microsoft.Net/assembly/GAC_MSIL/System.Xml/v4.0_4.0.0.0__b77a5c561934e089/System.Xml.dll
Location: C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System.Xml\v4.0_4.0.0.0__b77a5c561934e089\System.Xml.dll
Number of Types: 1229
Number of Custom Attributes:  25
.NET Runtime Version: v4.0.30319
================================================================
Assembly: MyConsoleApplication, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
================================================================
CodeBase: file:///C:/Temp/ReflectionWalkthroughSolution/MyConsoleApplication/bin/Debug/MyConsoleApplication.EXE
Location: C:\Temp\ReflectionWalkthroughSolution\MyConsoleApplication\bin\Debug\MyConsoleApplication.exe
Number of Types: 1
Number of Custom Attributes:  14
.NET Runtime Version: v4.0.30319

Pretty simple, right? Now let’s dig into the Assembly a little deeper.

Getting Types within an Assembly (Assembly.GetTypes() Method)

Every Assembly can contain one of more Types. We can retrieve all of these types via the Assembly.GetTypes() method. To try this out, we replace the code in our console application’s Program class’s Main() method with this code:

static void Main(string[] args)
{
    Assembly thisAssembly = Assembly.GetExecutingAssembly();

    //get all of the Types defined in this Assembly
    Type[] types = thisAssembly.GetTypes();

    //robust code always checks for null FIRST
    if (types != null && types.Length > 0)
    {
        //we'll create a StringBuilder for our formatted output
        StringBuilder sb = new StringBuilder();

        //iterate through the Type[] array
        foreach (Type type in types)
        {
            sb.AppendLine("===============================================================");
            sb.AppendLine(String.Format("Type Name: {0}", type.Name));
            sb.AppendLine("===============================================================");

            sb.AppendLine(String.Format("Type FullName: {0}", type.FullName));
            sb.AppendLine(String.Format("Namespace: {0}", type.Namespace));

            sb.AppendLine(String.Format("Is it a Class?: {0}", type.IsClass.ToString()));
            sb.AppendLine(String.Format("Is it an Interface?: {0}", type.IsInterface.ToString()));
            sb.AppendLine(String.Format("Is it Generic?: {0}", type.IsGenericType.ToString()));
            sb.AppendLine(String.Format("Is it Public?: {0}", type.IsPublic.ToString()));
            sb.AppendLine(String.Format("Is it Sealed?: {0}", type.IsSealed.ToString()));

            sb.AppendLine(String.Format("Qualified Name: {0}", type.AssemblyQualifiedName));

            if (type.BaseType != null && !String.IsNullOrEmpty(type.BaseType.Name))
            {
                sb.AppendLine(String.Format("Base Type: {0}", type.BaseType.Name));
            }

            //there are many, many more properties that an be shown...
        }

        string output = sb.ToString();
    }
}

In our console application, there is only one type, the Program class. When we execute the code above we see information for that single type.

===============================================================
Type Name: Program
===============================================================
Type FullName: MyConsoleApplication.Program
Namespace: MyConsoleApplication
Is it a Class?: True
Is it an Interface?: False
Is it Generic?: False
Is it Public?: False
Is it Sealed?: False
Qualified Name: MyConsoleApplication.Program, MyConsoleApplication, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
Base Type: Object

Now that we’ve moved down a level and looked at Types within an assembly, let’s move down one more level.

The next post discusses the System.Reflection.Type class and its properties and methods.

Using C# to Manage IIS – Microsoft.Web.Administration Namespace

Posted on Updated on

Introduction

If you have ever attempted to programmatically manipulate IIS in version 6 or earlier, you discovered that it is not a trivial endeavor! You could use Windows Management Instrumentation (WMI), ADSI, or write code to manually manipulate XML. It just wasn’t a task for the faint of heart! I have done it several times and remember it to be quite a headache.

Beginning with IIS 7, programmatic management of IIS has become much simpler with the Microsoft.Web.Administration objects. Using this API model, you can create websites, application pools, applications, virtual directories, etc. with just a few lines of code! Seriously!

The top-level objects of the API are illustrated below.

mwa_overview

This post will walk you through some of the features of the Microsoft.Web.Administration namespace objects and provide some real-world examples of how to implement this really cool API.

Getting Started

If you have IIS installed on your development machine, you will be able to find the .dll in Windows\system32\inetsrv folder or you can use the Nuget package. Either way, you will need the Microsoft.Web.Administration.dll file as a reference in your project.

Once you add the necessary reference to your project, you are ready to go!

Working with the ServerManager

To get started, we must first create an instance of the ServerManager class. We do this exactly as you might think:

ServerManager server = new ServerManager();

NOTE: It is important to note that an instance of the ServerManager assumes that the machine on which the application is running is the web server. It is possible to connect to a remote server using the ServerManager, and we will look at that later in this post.

Getting Sites Associated with the Server

Let’s get a list of all of the websites within our ServerManager. To do this, we access the Sites property of the ServerManager instance. The code block iterates each Site object.

 ServerManager server = new ServerManager();

SiteCollection sites = server.Sites;
foreach (Site site in sites)
{
    ApplicationDefaults defaults = site.ApplicationDefaults;

    //get the name of the ApplicationPool under which the Site runs
    string appPoolName = defaults.ApplicationPoolName;

    ConfigurationAttributeCollection attributes =  defaults.Attributes;
    foreach (ConfigurationAttribute configAttribute in attributes)
    {
        //put code here to work with each ConfigurationAttribute
    }

    ConfigurationAttributeCollection attributesCollection = site.Attributes;
    foreach (ConfigurationAttribute attribute in attributesCollection)
    {
        //put code here to work with each ConfigurationAttribute
    }

    //Get the Binding objects for this Site
    BindingCollection bindings = site.Bindings;
    foreach (Microsoft.Web.Administration.Binding binding in bindings)
    {
        //put code here to work with each Binding
    }

    //retrieve the State of the Site
    ObjectState siteState = site.State;

    //Get the list of all Applications for this Site
    ApplicationCollection applications = site.Applications;
    foreach (Microsoft.Web.Administration.Application application in applications)
    {
        //put code here to work with each Application
    }
}

For more information regarding all of the properties and methods of the Site class, click here.

Getting Applications Associated with each Site

The applications that run under a Site are represented through the Applications property of the Site class. The Applications property is an ApplicationCollection object. We can iterate each Application object with the following code:

ApplicationCollection applications = site.Applications;
foreach (Microsoft.Web.Administration.Application application in applications)
{
    //get the name of the ApplicationPool
    string applicationPoolName = application.ApplicationPoolName;

    VirtualDirectoryCollection directories = application.VirtualDirectories;
    foreach (VirtualDirectory directory in directories)
    {
        //put code here to work with each VirtualDirectory
    }
}

 Getting Virtual Directories Associated with each Application

The virtual directories associated with each Application are accessible via the VirtualDirectories property of the Application class. We can iterate each VirtualDirectory with the following code:

VirtualDirectoryCollection directories = application.VirtualDirectories;
foreach (VirtualDirectory directory in directories)
{
    ConfigurationAttributeCollection attribues = directory.Attributes;
    foreach (ConfigurationAttribute attribute in attributes)
    {
        //put code here to work with each attribute
    }

    ConfigurationChildElementCollection childElements = directory.ChildElements;
    foreach (ConfigurationElement element in childElements)
    {
        //put code here to work with each ConfigurationElement
    }

    //get the directory.Path
    string path = directory.Path;

    //get the physical path
    string physicalPath = directory.PhysicalPath;
}

Getting Application Pools Associated with a Server

The ServerManager.ApplicationPools property is an ApplicationPoolCollection object that contains all ApplicationPool objects associated with the server. Remember that this is a server-level property and that an ApplicationPool can be shared by multiple Sites and Applications.

ServerManager server = new ServerManager();

ApplicationPoolCollection applicationPools = server.ApplicationPools;
foreach (ApplicationPool pool in applicationPools)
{
    //get the AutoStart boolean value
    bool autoStart = pool.AutoStart;

    //get the name of the ManagedRuntimeVersion
    string runtime = pool.ManagedRuntimeVersion;

    //get the name of the ApplicationPool
    string appPoolName = pool.Name;

    //get the identity type
    ProcessModelIdentityType identityType = pool.ProcessModel.IdentityType;

    //get the username for the identity under which the pool runs
    string userName = pool.ProcessModel.UserName;

    //get the password for the identity under which the pool runs
    string password = pool.ProcessModel.Password;
}

Now that we have taken a quick look at the object hierarchy and iterated through some of the child collections, let’s switch our view to how to create and manipulate objects.

Creating a New Application Pool

To create a new ApplicationPool, we create a ServerManager instance, then check the ApplicationPools collection to see if our ApplicationPool already exists. If it does, we update a few of its properties, if not we create the new ApplicationPool. Regardless of the whether we are creating or updating, we use the ServerManager.CommitChanges() method.

ServerManager server = new ServerManager();

ApplicationPool myApplicationPool = null;

//we will create a new ApplicationPool named 'MyApplicationPool'
//we will first check to make sure that this pool does not already exist
//since the ApplicationPools property is a collection, we can use the Linq FirstOrDefault method
//to check for its existence by name
if (server.ApplicationPools != null && server.ApplicationPools.Count > 0)
{
    if (server.ApplicationPools.FirstOrDefault(p => p.Name == "MyApplicationPool") == null)
    {
        //if we find the pool already there, we will get a referecne to it for update
        myApplicationPool = server.ApplicationPools.FirstOrDefault(p => p.Name == "MyApplicationPool");
    }
    else
    {
        //if the pool is not already there we will create it
        myApplicationPool = server.ApplicationPools.Add("MyApplicationPool");
    }
}
else
{
    //if the pool is not already there we will create it
    myApplicationPool = server.ApplicationPools.Add("MyApplicationPool");
}

if (myApplicationPool != null)
{
    //for this sample, we will set the pool to run under the NetworkService identity
    myApplicationPool.ProcessModel.IdentityType = ProcessModelIdentityType.NetworkService;

    //we set the runtime version
    myApplicationPool.ManagedRuntimeVersion = "v4.0";

    //we save our new ApplicationPool!
    server.CommitChanges();
}

In the previous example, we set the ProcessModel.IdentityType to NetworkService. It’s a more common scenario to have a pre-defined service account that is most likely a domain account reserved specifically for this purpose. In this case, we can make a small modification to our code to implement this change. See below.

if (myApplicationPool != null)
{
    //for this sample, we will set the pool to run under the identity of a specific user
    myApplicationPool.ProcessModel.IdentityType = ProcessModelIdentityType.SpecificUser;
    myApplicationPool.ProcessModel.UserName = UserName;
    myApplicationPool.ProcessModel.Password = Password;

    //we set the runtime version
    myApplicationPool.ManagedRuntimeVersion = "v4.0";

    //we save our new ApplicationPool!
    server.CommitChanges();
}

The UserName and Password values are strings that we would provide to our code when creating the ApplicationPool.

Creating a New Site

Creating a new Site is just as easy as creating a new ApplicationPool!

ServerManager server = new ServerManager();

if (server.Sites != null && server.Sites.Count > 0)
{
    //we will first check to make sure that the site isn't already there
    if (server.Sites.FirstOrDefault(s => s.Name == "MySite") == null)
    {
        //we will just pick an arbitrary location for the site
        string path = @"c:\MySiteFolder\";

        //we must specify the Binding information
        string ip = "*";
        string port = "80";
        string hostName = "*";

        string bindingInfo = string.Format(@"{0}:{1}:{2}", ip, port, hostName);

        //add the new Site to the Sites collection
        Site site = server.Sites.Add("MySite", "http", bindingInfo, path);

        //set the ApplicationPool for the new Site
        site.ApplicationDefaults.ApplicationPoolName = myApplicationPool.Name;

        //save the new Site!
        server.CommitChanges();
    }
}

We have only touched on some of the functionality provided to us by the Microsoft.Web.Administration namespace objects. For more information, this MSDN resource is a great place to start. Remember that this API is only valid for IIS version 7.0 and higher and that if you seek to automate a prior version your plan of attack will be quite different.

How to Find the Public Key Token for a Strongly-Named Assembly

Posted on Updated on

In another post, we discussed why you should strongly name assemblies and how to do it via the Strong Name Tool (sn.exe). In this quick post we will consider another scenario – how to get the public key token string value of a strongly named assembly (dll).

This is easily accomplished by using the Strong Name Tool as well by simply using the -T switch and specifying the path and name of the dll for which to retrieve the public key token. It can also be done in code using the System.Reflection.Assembly class. We will take a quick look at each approach.

Using the Strong Name Tool

To use the Strong Name Tool (sn.exe), simply open the Microsoft Visual Studio Developer Command Prompt and use the following syntax:

sn.exe -T <assembly path and name>

For this example, we will retrieve the public key token for an assembly named SampleClassLibrary.dll that is located in the c:\Temp folder.

Get Public Key Token of Strongly Named Assembly using Strong Name Tool

For this example, the public key token is ba27e13c07659075.

Using the System.Reflection.Assembly Class – Getting the Public Key Token in Code

The manner in which we get the public key token for an assembly depends upon our point of reference. For example, we can do it for the currently executing assembly, do it for a specific assembly based on a file path, or do it for one or all assemblies that have been loaded within the current AppDomain.

Let’s consider two of these scenarios:

Getting the Public Key Token for the Currently Executing Assembly

To demonstrate this approach, I have created a simple method in a sample class:

public void GetPublicKeyTokenForThisAssembly()
{
    byte[] token = Assembly.GetExecutingAssembly().GetName().GetPublicKeyToken();
}

Now, if we execute this code, and place a breakpoint on the line that gets the byte array, we can use Visual Studio debugging to view the results of our token retrieval.

PublicKeyTokenDebuggerView

Since the return type is a byte array, the results are exactly as we would expect. So how do we convert that to the same string that we saw when we did it through the Strong Name Tool?

We write a little bit of C# code to do this as shown below:

byte[] token = Assembly.GetExecutingAssembly().GetName().GetPublicKeyToken();

StringBuilder sb = new StringBuilder();

for (int i = 0; i < token.GetLength(0); i++)
{
     sb.Append(String.Format("{0:x2}", token[i]));
}

string publicKeyToken = sb.ToString();

The result of this is the public key token that we retrieved using the Strong Name Tool above.

If you are wondering what the “{0:x2}” formatter does, this is something known as Composite Formatting. The hexadecimal format specifier (X) converts a number to a string of hexadecimal digits. The “2” instructs the runtime to produce a string with a length of 2.

Getting the Public Key Token for Assemblies in the Current AppDomain

Though the point of this post is not to discuss Reflection or how to use the methods of the Assembly class, it is beneficial to take a look at how this same logic could be applied to all assemblies loaded within the current AppDomain.

To do this, we merely have to change our code to get the assemblies and iterate through them to retrieve the public key tokens.

byte[] token;

StringBuilder publicKeyTokens = new StringBuilder();
Assembly[] loadedAssemblies = AppDomain.CurrentDomain.GetAssemblies();

foreach (Assembly assembly in loadedAssemblies)
{
    token = assembly.GetName().GetPublicKeyToken();
    StringBuilder sb = new StringBuilder();

    for (int i = 0; i < token.GetLength(0); i++)
    {
        sb.Append(String.Format("{0:x2}", token[i]));
    }

    publicKeyTokens.AppendLine("The public key token for assembly " + assembly.FullName + " is:  " + sb.ToString());
}

string allPublicKeyTokens = publicKeyTokens.ToString();

The output of our StringBuilder is as follows:

The public key token for assembly mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 is:  b77a5c561934e089
The public key token for assembly Microsoft.VisualStudio.HostingProcess.Utilities, Version=11.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a is:  b03f5f7f11d50a3a
The public key token for assembly System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 is:  b77a5c561934e089
The public key token for assembly System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a is:  b03f5f7f11d50a3a
The public key token for assembly System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 is:  b77a5c561934e089
The public key token for assembly Microsoft.VisualStudio.HostingProcess.Utilities.Sync, Version=11.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a is:  b03f5f7f11d50a3a
The public key token for assembly Microsoft.VisualStudio.Debugger.Runtime, Version=11.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a is:  b03f5f7f11d50a3a
The public key token for assembly vshost32, Version=11.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a is:  b03f5f7f11d50a3a
The public key token for assembly System.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 is:  b77a5c561934e089
The public key token for assembly System.Xml.Linq, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 is:  b77a5c561934e089
The public key token for assembly System.Data.DataSetExtensions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 is:  b77a5c561934e089
The public key token for assembly Microsoft.CSharp, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a is:  b03f5f7f11d50a3a
The public key token for assembly System.Data, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 is:  b77a5c561934e089
The public key token for assembly System.Deployment, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a is:  b03f5f7f11d50a3a
The public key token for assembly System.Xml, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 is:  b77a5c561934e089
The public key token for assembly WindowsFormsApplication2, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null is:
The public key token for assembly SampleClassLibrary, Version=1.0.0.0, Culture=neutral, PublicKeyToken=ba27e13c08759075 is:  ba27e13c08759075

If you notice the assembly for my sample WinForms assembly shows a PublicKeyToken that is null. Why is this? Because I did not strongly name that assembly! Also, notice that in our code we extracted the FullName value for each assembly. This is an important point when talking about strong naming because the FullName shows us the name, version, public key token, and culture information.

In this simple little sample, I utilized a solution with two projects: a WinForms project and a class library that is referenced by the WinForms project. In this sample, the class library is strongly named and the WinForms project is not. This is okay, but the opposite scenario is not. Suppose that we strongly name the WinForms assembly but remove the strong name from the class library. The compiler will bark at us then we try to build the solution because as we said before, a strongly named assembly requires all referenced assemblies to be strongly named as well. The output below is generated when we try to build under the second scenario:

strongnamedcompilererror

 

 

Strong-Named Assemblies – Why and How

Posted on Updated on

Overview

In this short post, I will discuss strong-named assemblies in .NET and cover why and how you should use strong naming within your .NET projects.

First, there seems to be a misconception among many developers with whom I’ve worked that strong naming is a security measure. That is not the intent of strong naming! Instead, strong naming provides a means of uniquely identifying assemblies and providing evidence that the assembly to be loaded is indeed the one intended. A strong name consists of the following things:

  • The assembly’s simple text name
  • The assembly’s full version number
  • The assembly’s culture information
  • A Public Key
  • A Digital Signature

Why Use Strong-Named Assemblies?

Based on your scenario, there are some distinct reasons why assemblies should be strongly named.

  • If you plan to deploy your assembly to the Global Assembly Cache (GAC), the assembly MUST be strongly named. This is necessary because the GAC allows multiple versions of the same assembly to coexist simultaneously. Anyone who has been around long enough to remember Windows development in the past, especially prior to .NET remember the term “DLL Hell”. I know this term all too well and learned very quickly a long time ago what a pain it can be. Those are bad memories, so I will move on! I will discuss the Global Assembly Cache in another article.
  • Strong names protect the version lineage of an assembly. If an assembly is strongly named, the assemblies it references must also be strongly named.

Strong naming relies upon asymmetric cryptography. Although the purpose of this article is strong naming, a very quick discussion of asymmetric cryptography is warranted. I will only take a shallow dive into asymmetric cryptography because this purpose of this article is to briefly discuss strong-named assemblies and not encryption.

Asymmetric Cryptography (Public Key Cryptography)

Public Key Cryptography (PKC) is a form of cryptography that requires two “keys” – a public key and a private key. The public key is known to all and the private key is only known to the individual. In a very simple example of PKC, if I decide to send an encrypted message to one of my friends, I use my friend’s public key to encrypt the message, then he uses his private key to decrypt it. If he then decides to send me an encrypted message, he uses my public key to encrypt it then I use my private key to decrypt. Makes sense, right? It is important to note that with PKC, only the public key can be used to encrypt, and only the corresponding private key can be used to decrypt.

How to Create a Strong Name Key Pair

Before strongly naming an assembly, you must first have a public/private key pair file. The key pair is used by the compiler to create the strong-named assembly.

Creating a strong name key pair is made very simple by Microsoft through use of the Strong Name Tool (sn.exe). To create a key pair using this tool, simply follow these steps:

  1. From the Start menu, select your version of Visual Studio in the All Programs menu, click Visual Studio Tools, then select the Visual Studio Developer Command Prompt menu. See the following image.
  2. When the Visual Studio Command Prompt window opens, invoke the Strong Name Tool using the following syntax: sn -k KeyFileName.snk. The Strong Name Tool has numerous switches that can be used with its invocation. The -k switch instructs the tool to create the public/private key pair with the name that you specify. You can specify the path to which the .snk file should be created. In the example below, I create a key pair named MyKeyPair and I write it to the c:\Temp folder.sn_result
  3. When the key pair has been created, the next step is to use it to strongly-name an assembly. This is easily accomplished within Visual Studio by right-clicking the project to be signed and selecting the Properties menu. The example below shows the Properties window for a sample class library that I have created.SigningTabBrowse
  4. Browse to the .snk file and click Ok. The .snk is then associated with the project and when built, the resulting assembly will be strongly-named.

That’s it! Pretty simple. In the next article we take a look at how to extract the PublicKeyToken from a strongly-named assembly.