.NET

Binary Serialization with C# and .NET

Posted on Updated on

In the previous post, we discussed XML serialization. In this short article we are going to take a look at binary serialization via the BinaryFormatter class. As we mentioned in a previous article, binary serialization in .NET converts an object or entire object graph into a binary format that is not human readable. Binary serialization is sometimes called “deep” serialization because it serializes the entire object state, all relations among an object graph, and all references to other objects. It preserves type fidelity which is quite useful when utilizing objects across multiple applications of instances of the same application.

Binary Serialization with the BinaryFormatter class

To get started, let’s create a simple class, add some attributes to help us control how properties are serialized, then we’ll serialize and deserialize the object.

To get started, let’s create a C# Console Application that we will call BinarySerializationSample.

Create Binary Serialization Sample Application
Create Binary Serialization Sample Application

Next, we will add a class to the project. We will call this class Sample.

Let’s now add two properties – Name and Value¬†as shown below then decorate the class with the Serializable attribute.

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

namespace SerializationSample
{
    [Serializable]
    public class Sample
    {
        public string Name
        {
            get;
            set;
        }

        public Int32 Value
        {
            get;
            set;
        }
    }
}

Before we can use binary serialization, we have to add a reference to the System.Runtime.Serialization assembly. Right-click the project, select Add Reference, then select this assembly in the dialog. See below.

System.Runtime.Serialization Assembly Reference
System.Runtime.Serialization Assembly Reference

Now that we have created our Sample class, decorated it with the Serializable attribute, and added a reference to the System.Runtime.Serialization assembly, let’s jump to our Program class’s Main() method and write some simple code to serialize an instance of our object. The full code for our Program class is shown below.

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using System.Threading.Tasks;

namespace BinarySerializationSample
{
    class Program
    {
        static void Main(string[] args)
        {
            //create an instance of our Sample object
            Sample sample = new Sample();
            sample.Name = "John Nelson";
            sample.Value = 44;

            //create a FileStream to write the serialized output
            //to a file on our hard drive
            FileStream fileStream = new FileStream(@"c:\Temp\Sample.dat", FileMode.Create);

            //create a BinaryFormatter object to serialize our object
            BinaryFormatter formatter = new BinaryFormatter();
            formatter.Serialize(fileStream, sample);

        }
    }
}

Now let’s press F5 and run our console application. We will open the file with Wordpad so that the text wraps and so that we can see the contents. See below.

Output of Binary Serialization
Output of Binary Serialization

The binary output does have some readable text. The first thing you should notice is that the entire assembly and namespace information is stored with the serialized object. This is something that XML serialization does NOT do. Pretty cool.

Be sure not to save the file because we are going to use this same file in the code below to deserialize our object.

//create a FileStream to read the serialized object
FileStream fileStream = new FileStream(@"c:\Temp\Sample.dat", FileMode.Open);

//create a BinaryFormatter and deserialize the object
BinaryFormatter formatter = new BinaryFormatter();
Sample deserializedSample = (Sample)formatter.Deserialize(fileStream);

Console.WriteLine("The deserialized object:");
Console.WriteLine(String.Format("Name: {0}", deserializedSample.Name));
Console.WriteLine(String.Format("Value: {0}", deserializedSample.Value.ToString()));

Console.Read();

When we press F5 and run our application, we see the following output in our console window:

Binary Deserialization Output
Binary Deserialization Output

Pretty cool, right? Yes it is.

Now this was a very simple example but understand that any object that supports serialization can be serialized using the BinaryFormatter. If we have an object graph that contained multiple levels of objects in a hierarchy, we could serialize these objects in exactly the same way that we just did. I have written numerous applications that have required objects to be serialized and stored in a database. Again, we use the same approach, then we execute a stored procedure or invoke an insert or update call and pass the binary data into the table. No big deal.

In the next post, we will take a look at SOAP Serialization with C# and .NET.

XML Serialization with C# and .NET

Posted on Updated on

In the previous post, we discussed the basics of serialization in .NET. In this short article we are going to dive into some specific examples of use of the XmlSerializer to serialize objects to XML and deserialize from XML back into objects ūüôā

XML Serialization with the System.Xml.Serialization.XmlSerializer class

To get started, let’s create a simple class, add some attributes to help us control how properties are serialized, then we’ll serialize and deserialize the object.

To get started, let’s create a C# Console Application that we will call SerializationSample.

Create the Serialization Sample Console Application
Create the Serialization Sample Console Application

Next, we will add a class to the project. We will call this class Sample.

Let’s now add two properties – Name and Value¬†as shown below.

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

namespace SerializationSample
{
    public class Sample
    {
        public string Name
        {
            get;
            set;
        }

        public Int32 Value
        {
            get;
            set;
        }
    }
}

As you can see, this class is very simple and is a great starting point for examining Xml serialization.

In our Program.cs class’s Main() method, we are going to write some simple code that uses the XmlSerializer to serialize an instance of our Sample object into an XML stream.

static void Main(string[] args)
{
    //let's first instantiate our Sample object
    //and set its two properties
    Sample sample = new Sample();
    sample.Name = "John Nelson";
    sample.Value = 44;

    //next, create an instance of an XmlSerializer
    //we will use the Type constructor for this example
    XmlSerializer serializer = new XmlSerializer(typeof(Sample));

    //serialize the object to the Console
    serializer.Serialize(Console.Out, sample);

    Console.ReadLine();
}

When we press F5 and run the console application, our console window displays the resulting XML. See the following illustration.

XMLSerializer.Serialize Output
XMLSerializer.Serialize Output

So when we look at the resulting XML, we see the standard xml declaration followed by the opening tag for our Sample object, an XML namespace (xmlns) attribute, and the values of the two properties in our object. Pretty simple, right?

XML Namespaces

Though my intent in this article is to discuss XML serialization and not XML and its accompanying concepts, we do need to briefly talk about the concept of XML namespaces. XML namespaces provide a couple of things in our generated XML. First, they remove any ambiguity between two or more elements that happen to have the same name. Second, they are useful for grouping elements that relate and adhere to a common “idea” together.

A namespace is identified and conveyed via a uri. So what does that mean? Well, I could define a namespace that I could use for all of my XML “objects” based on http://johnlnelson.com. I could for the sake of this example specify the namespace to be https://johnlnelson.com/namespaces/sample. It is a safe assumption that anyone who produces XML for the purpose of transmitting it via HTTP would do this via a domain, such as johnlnelson.com.

Armed with this little bit of knowledge, let’s modify our code to specify our own namespace and prefix.

static void Main(string[] args)
{
    //let's first instantiate our Sample object
    //and set its two properties
    Sample sample = new Sample();
    sample.Name = "John Nelson";
    sample.Value = 44;

    //next, create an instance of an XmlSerializer
    //we will use the Type constructor for this example
    XmlSerializer serializer = new XmlSerializer(typeof(Sample));

    //specify our namespace
    XmlSerializerNamespaces namespaces = new XmlSerializerNamespaces();
    namespaces.Add("jln", "https://johnlnelson.com/namespaces/sample");

    //serialize the object to the Console
    //with our specified namespace
    serializer.Serialize(Console.Out, sample, namespaces);

    Console.ReadLine();
}

Pay attention to lines 14-15 and the new overload of the Serialize() method in line 19.

We created an instance of an XmlSerializerNamespaces object and added one prefix/namespace combination. The prefix I chose is “jln” and my namespace is https://johnlnelson.com/namespaces/sample.

When we press F5 to launch the application and view our XML output, we see our own namespace in the XML.

XMLSerializer.Serialize Output with Namespace
XMLSerializer.Serialize Output with Namespace

Pretty simple? Sure it is. Now let’s take a look at the encoding attribute in our xml document tag. What if we don’t want to use the default IBM437 encoding? We can do that too, but the code is slightly more involved (but not too bad).

Let’s modify our serialization code to look like this:

//let's first instantiate our Sample object
//and set its two properties
Sample sample = new Sample();
sample.Name = "John Nelson";
sample.Value = 44;

//next, create an instance of an XmlSerializer
//we will use the Type constructor for this example
XmlSerializer serializer = new XmlSerializer(typeof(Sample));

//specify our namespace
XmlSerializerNamespaces namespaces = new XmlSerializerNamespaces();
namespaces.Add("jln", "https://johnlnelson.com/namespaces/sample");

//create an XmlWriterSettings object to specify the
//encoding and the indentation
XmlWriterSettings settings = new XmlWriterSettings();
settings.Encoding = new UTF8Encoding();
settings.Indent = true;

//create an XmlWriter that utilizes a StringWriter to
//build the output, then write that to the Console window
using (StringWriter stringWriter = new StringWriter())
{
    using (XmlWriter xmlWriter = XmlWriter.Create(stringWriter, settings))
    {
        serializer.Serialize(xmlWriter, sample, namespaces);
        Console.Write(stringWriter);
    }
}

Console.ReadLine();

You will see quite a few changes from lines 17 to 30. We will discuss the changes in a bit, but first let’s press F5 and run our code! The console window will now look like this:

XMLSerializer.Serialize() Output
XMLSerializer.Serialize() Output

Okay, so what’s going on here? First, in order to take the level of control over our encoding, we had to initiate the help of a StringWriter and an XmlWriter. We created an XmlWriterSettings object to allow us to specify the encoding and the indentation for our XML output. Then we wrapped the XmlWriter in a StringWriter which we then used to send our output to the Console window.

We won’t go into too much detail about StringWriters and XmlWriters in this article, but let’s just take from this the fact that it is possible to exert varying levels of control over our XML serialization.

Controlling How We Serialize

Before we jump into deserialization, let’s hop back to our Sample class and take a look at some ways we can control how instances of this type are XML serialized. This sample class has two properties: Name and Value. As it stands now, both properties get serialized when we call the XmlSerializer.Serialize() method. Let’s look at some ways we can control this.

System.Xml.Serialization.XmlIgnoreAttribute

Using the XmlIgnore attribute we can instruct the XmlSerializer to ignore public fields or properties when XML serialization takes place. For this example, let’s add an XmlIgnore attribute to the Value property as shown below:

[XmlIgnore]
public Int32 Value
{
    get;
    set;
}

By adding this simple attribute, the XmlSerializer will not serialize this property. Let’s press F5 and give it a try.

When we place a breakpoint and view the XML output in the XML view window, we notice that the Value property did NOT get serialized.

XmlSerializer.Serialize() method with Value property ignored
XmlSerializer.Serialize() method with Value property ignored

Pretty simple, right?

System.Xml.Serialization.XmlElementAttribute

What if we want to serialize a property, but we want the XmlElement to have a name other than the actual name in the object? This is equally simple by decorating that property with an XmlElement attribute. See the code below.

[XmlElement (ElementName="TheValue")]
public Int32 Value
{
    get;
    set;
}

This attribute instructs the XmlSerializer to serialize the property, but to name the XmlElement “TheValue”. Again, we place a breakpoint and view the output:

XmlSerializer.Serialize() method with XmlElement attribute on Value property
XmlSerializer.Serialize() method with XmlElement attribute on Value property

As we can see in the output, the Value field is serialized as TheValue. Pretty simple.

There are more customization options available through attributes, but we are not going to cover all of them here. This MSDN resource is an excellent source of information.

Deserializing XML with C#

Now that we have discussed XML serialization, let’s look quickly at how to deserialize our XML and re-inflate a Sample object.

We will modify the code in our Program class’s Main() method to look like this:

//let's first instantiate our Sample object
//and set its two properties
Sample sample = new Sample();
sample.Name = "John Nelson";
sample.Value = 44;

//next, create an instance of an XmlSerializer
//we will use the Type constructor for this example
XmlSerializer serializer = new XmlSerializer(typeof(Sample));

//specify our namespace
XmlSerializerNamespaces namespaces = new XmlSerializerNamespaces();
namespaces.Add("jln", "https://johnlnelson.com/namespaces/sample");

//serialize the object to a StringWriter
//with our specified namespace
StringWriter writer = new StringWriter();
serializer.Serialize(writer, sample, namespaces);

//for our subsequent deserialization, we will
//get the StringWriter's underlying StringBuilder
string xml = writer.GetStringBuilder().ToString();

//we will create a new StringReader using
//the xml string created above
TextReader reader = new StringReader(xml);

//we will deserialize the reader and cast
//the resulting object to a Sample type
Sample deserialized = (Sample)serializer.Deserialize(reader);

//finally, we will write our object to the Console
Console.WriteLine("We have deserialized our object");
Console.WriteLine(String.Format("Name: {0}", deserialized.Name));
Console.WriteLine(String.Format("Value: {0}", deserialized.Value.ToString()));
Console.WriteLine("That was cool!");

Console.ReadLine();

The commentary in the code is pretty straightforward. We serialized our object into a StringWriter then got that object’s underlying StringBuilder and got its string representation via the xml string object. Then we created a StringReader to utilize the XML string value as an input to our XmlSerializer.Deserialize() method. We immediately cast the return object to the Sample type because we knew that it would work. In most situations like this, should NOT attempt such a cast. Instead, you should stuff the result into an object, then check for null BEFORE attempting the cast. That is just a good programming practice.

When we press F5 to run the application, we see the following output:

XMLSerializer.Deserialize() Output
XMLSerializer.Deserialize() Output

In the next article, we will take a quick look at binary serialization.

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.