Chapter 15. Assemblies and Versioning

In .NET, the basic unit deployable is called an assembly. Assemblies play an important part of the development process where understanding how they work is useful in helping you develop scalable, efficient .NET applications. This chapter explores:

  • The components that make up a .NET assembly

  • The difference between single-file and multi-file assemblies

  • The relationships between namespaces and assemblies

  • The role played by the Global Assembly Cache (GAC)

  • How to develop a shared assembly, which can be shared by other applications

Assemblies

In .NET, an assembly takes the physical form of an EXE (known as a process assembly) or DLL (known as a library assembly) file, organized in the Portable Executable (PE) format. The PE format is a file format used by the Windows operating system for storing executables, object code, and DLLs. An assembly contains code in IL (Intermediate Language; compiled from a .NET language), which is then compiled into machine language at runtime by the Common Language Runtime (CLR) just-in-time compiler.

Structure of an Assembly

An assembly consists of the following four parts (see Figure 15-1).

Part

Description

Assembly metadata

Describes the assembly and its content

Type metadata

Defines all the types and methods exported from the assembly

IL code

Contains the MSIL code compiled by the compiler

Resources

Contains icons, images, text strings, as well as other resources used by your application

Figure 15-1

Figure 15.1. Figure 15-1

Physically, all four parts can reside in one physical file, or some parts of an assembly can be stored other modules. A module can contain type metadata and IL code, but it does not contain assembly metadata. Hence, a module cannot be deployed by itself; it must be combined with an assembly to be used. Figure 15-2 shows part of an assembly stored in two modules.

Figure 15-2

Figure 15.2. Figure 15-2

An assembly is the basic unit of installation. In this example, the assembly is made up of three files (one assembly and two modules). The two modules by themselves cannot be installed separately; they must accompany the assembly.

Examining the Content of an Assembly

As mentioned briefly in Chapter 1, you can use the MSIL Disassembler tool (ildasm.exe) to examine the content of an assembly. Figure 15-3 shows the tool displaying an assembly's content.

Figure 15-3

Figure 15.3. Figure 15-3

Among the various components in an assembly, the most important is the manifest (shown as MANIFEST in Figure 15-3), which is part of the assembly metadata. The manifest contains information such as the following:

  • Name, version, public key, and culture of the assembly

  • Files belonging to the assembly

  • References assemblies (other assemblies referenced by this assembly)

  • Permission sets

  • Exported types

Figure 15-4 shows the content of the manifest of the assembly shown in Figure 15-3.

Figure 15-4

Figure 15.4. Figure 15-4

Single and Multi-File Assemblies

In Visual Studio, each project that you create will be compiled into an assembly (either EXE or DLL). By default, a single-file assembly is created. Imagine you are working on a large project with10 other programmers. Each one of you is tasked with developing part of the project. But how do you test the system as a whole? You could ask every programmer in the team to send you his or her code and then you could compile and test the system as a whole. However, that really isn't feasible, because you have to wait for everyone to submit his or her source code. A much better way is to get each programmer to build his or her part of the project as a standalone library (DLL). You can then get the latest version of each library and test the application as a whole. This approach has an added benefit — when a deployed application needs updating, you only need to update the particular library that needs updating. This is extremely useful if the project is large. In addition, organizing your project into multiple assemblies ensures that only the needed libraries (DLLs) are loaded during runtime.

To see the benefit of creating multi-file assemblies, let's create a new Class Library project, using Visual Studio 2008, and name it MathUtil. In the default Class1.cs, populate it with the following code:

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

namespace MathUtil
{
    public class Utils
    {
        public int Fibonacci(int num)
        {
            if (num <= 1) return 2; //---should return 1; error on purpose---
            return Fibonacci(num - 1) + Fibonacci(num - 2);
        }
    }
}

This Utils class contains a method called Fibonacci(), which returns the nth number in the Fibonacci sequence (note that I have purposely injected an error into the code so that I can later show you how the application can be easily updated by replacing the DLL). Figure 15-5 shows the first 20 numbers in the correct Fibonacci sequence.

Figure 15-5

Figure 15.5. Figure 15-5

Build the Class Library project (right-click on the project's name in Solution Explorer, and select Build) so that it will compile into a DLL — MathUtil.dll.

Add a Windows Application project to the current solution, and name it WindowsApp-Util. This application will use the Fibonacci() method defined in MathUtil.dll. Because the MathUtil.dll assembly is created in the same solution as the Windows project, you can find it in the Projects tab of the Add Reference dialog (see Figure 15-6). Select the assembly, and click OK.

Figure 15-6

Figure 15.6. Figure 15-6

The MathUtil.dll assembly will now be added to the project. Observe that the Copy Local property for the MathUtil.dll assembly is set to True (see Figure 15-7). This means that a copy of the assembly will be placed in the project's output directory (that is, the binDebug folder).

Figure 15-7

Figure 15.7. Figure 15-7

Note

When you add a reference to one of the classes in the .NET class library, the Copy Local property for the added assembly will be set to False. That's because the .NET assembly is in the Global Assembly Cache (GAC), and all computers with the .NET Framework installed have the GAC. The GAC is discussed later in this chapter.

Switch to the code-behind of the default Form1 and code the following statements:

namespace WindowsApp_Util
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            CallUtil();
        }

        private void CallUtil()
        {
            MathUtil.Utils util = new MathUtil.Utils();
            MessageBox.Show(util.Fibonacci(7).ToString());
        }
    }
}

Set a breakpoint at the CallMathUtil() method (see Figure 15-8).

Figure 15-8

Figure 15.8. Figure 15-8

Right-click on the WindowsApp-Util project name in Solution Explorer, and select Start as Startup Project. Press F5 to debug the application. When the application stops at the breakpoint, view the modules loaded into memory by selecting Debug

Figure 15-8
Figure 15-9

Figure 15.9. Figure 15-9

Observe that MathUtil.dll library has not been loaded yet. Press F11 to step into the CallMathUtil() function (see Figure 15-10). The MathUtil.dll library is now loaded into memory.

Figure 15-10

Figure 15.10. Figure 15-10

Press F5 to continue the execution. You should see a message box displaying the value 42. In the binDebug folder of the Windows application project, you will find the EXE assembly as well as the DLL assembly (see Figure 15-11).

Figure 15-11

Figure 15.11. Figure 15-11

Updating the DLL

The Fibonacci() method defined in the MathUtil project contains a bug. When num is less than or equal to 1, the method should return 1 and not 2. In the real world, the application and the DLL may already been deployed to the end user's computer. To fix this bug, you simply need to modify the Utils class, recompile it, and then update the user's computer with the new DLL:

namespace MathUtil
{
    public class Utils
    {
        public int Fibonacci(int num)
        {
            if (num <= 1) return 1; //---fixed!---
            return Fibonacci(num - 1) + Fibonacci(num - 2);
        }
    }
}

Copy the recompiled MathUtil.dll from the binDebug folder of the MathUtil project, and overwrite the original MathUtil.dll located in the binDebug folder of the Windows project. When the application runs again, it will display the correct value, 21 (previously it displayed 42).

Because the MathUtil.dll assembly is not digitally signed, a hacker could replace this assembly with one that contains malicious code, and the client of this assembly (which is the WindowsApp-Util application in this case) would not know that the assembly has been tampered with. Later in this chapter, you will see how to give the assembly a unique identity using a strong name.

Modules and Assemblies

An application using a library loads it only when necessary — the entire library is loaded into memory during runtime. If the library is large, your application uses up more memory and takes a longer time to load. To solve this problem, you can split an assembly into multiple modules and then compile each individually as a module. The modules can then be compiled into an assembly.

To see how you can use a module instead of an assembly, add a new Class Library project to the solution used in the previous section. Name the Class Library project StringUtil. Populate the default Class1.cs file as follows:

using System.Text.RegularExpressions;
namespace StringUtil
{
    public class Utils
    {
        public bool ValidateEmail(string email)
        {
            string strRegEx = @"^([a-zA-Z0-9_-.]+)@(([[0-9]{1,3}" +
                @".[0-9]{1,3}.[0-9]{1,3}.)|(([a-zA-Z0-9-]+" +
                @".)+))([a-zA-Z]{2,4}|[0-9]{1,3})(]?)$";
            Regex regex = new Regex(strRegEx);
            if (regex.IsMatch(email))
                return (true);
            else
                return (false);
        }
    }
}

Instead of using Visual Studio 2008 to build the project into an assembly, use the C# compiler to manually compile it into a module.

To use the C# compiler, launch the Visual Studio 2008 Command Prompt (Start

Modules and Assemblies

Navigate to the folder containing the StringUtil project, and type in the following command to create a new module:

csc /target:module /out:StringUtil.netmodule Class1.cs

When the compilation is done, the StringUtil.netmodule file is created (see Figure 15-12).

Figure 15-12

Figure 15.12. Figure 15-12

Do the same for the MathUtil class that you created earlier (see Figure 15-13):

csc /target:module /out:MathUtil.netmodule Class1.cs
Figure 15-13

Figure 15.13. Figure 15-13

Copy the two modules that you have just created — StringUtil.netmodule and MathUtil.netmodule — into a folder, say C:Modules. Now to combine these two modules into an assembly, type the following command:

csc /target:library /addmodule:StringUtil.netmodule /addmodule:MathUtil.netmodule /out:Utils.dll

This creates the Utils.dll assembly (see Figure 15-14).

Figure 15-14

Figure 15.14. Figure 15-14

In the WindowsApp-Utils project, remove the previous versions of the MathUtil.dll assembly and add a reference to the Utils.dll assembly that you just created (see Figure 15-15). You can do so via the Browse tab of the Add Reference dialog (navigate to the directory containing the modules and assembly, C:Modules). Click OK.

Figure 15-15

Figure 15.15. Figure 15-15

In the code-behind of Form1, modify the following code as shown:

namespace WindowsApp_Util
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            CallMathUtil();
            CallStringUtil();
        }

        private void CallMathUtil()
        {
            MathUtil.Utils util = new MathUtil.Utils();
            MessageBox.Show(util.Fibonacci(7).ToString());
        }

        private void CallStringUtil()
        {
            StringUtil.Utils util = new StringUtil.Utils();
            MessageBox.Show(util.ValidateEmail(
                "[email protected]").ToString());
        }
    }
}

The CallMathUtil() function invokes the method defined in the MathUtil module. The CallStringUtil() function invokes the method defined in the StringUtil module.

Set a break point in the Form1_Load event handler, as shown in Figure 15-16, and press F5 to debug the application.

Figure 15-16

Figure 15.16. Figure 15-16

When the breakpoint is reached, view the Modules window (Debug

Figure 15-16
Figure 15-17

Figure 15.17. Figure 15-17

Press F11 to step into the CallMathUtil() function, and observe that the Utils.dll assembly is now loaded, together with the MathUtil.netmodule (see Figure 15-18).

Figure 15-18

Figure 15.18. Figure 15-18

Press F11 a few times to step out of the CallMathUtil() function until you step into CallStringUtil(). See that the StringUtil.netmodule is now loaded (see Figure 15-19).

Figure 15-19

Figure 15.19. Figure 15-19

This example proves that modules in an assembly are loaded only as and when needed. Also, when deploying the application, the Util.dll assembly and the two modules must be in tandem. If any of the modules is missing during runtime, you will encounter a runtime error, as shown in Figure 15-20.

Figure 15-20

Figure 15.20. Figure 15-20

Understanding Namespaces and Assemblies

As you know from Chapter 1, the various class libraries in the .NET Framework are organized using namespaces. So how do namespaces relate to assemblies? To understand the relationship between namespaces and assemblies, it's best to take a look at an example.

Create a new Class Library project in Visual Studio 2008, and name it ClassLibrary1. In the default Class1.cs, populate it with the following:

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

namespace Learn2develop.net
{
    public class Class1
    {
        public void DoSomething()
        {
        }
    }
}

Observe that the definition of Class1 is enclosed within the Learn2develop.net namespace. The class also contains the DoSomething() method.

Add a new class to the project by right-clicking on the project's name in Solution Explorer and selecting Add

Understanding Namespaces and Assemblies
Figure 15-21

Figure 15.21. Figure 15-21

Use the default name of Class2.cs. In the newly added Class2.cs, code the following:

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

namespace Learn2develop.net
{
    public class Class2
    {
        public void DoSomething()
        {
        }
    }
}

Class2 is enclosed within the same namespace — Learn2develop.net, and it also has a DoSomething() method. Compile the ClassLibrary1 project so that an assembly is generated in the binDebug folder of the project — ClassLibrary1.dll. Add another Class Library project to the current solution and name the project ClassLibrary2 (see Figure 15-22).

Figure 15-22

Figure 15.22. Figure 15-22

Populate the default Class1.cs as follows:

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

namespace Learn2develop.net
{
    public class Class3
    {
        public void DoSomething()
        {
        }
    }
}

namespace CoolLabs.net
{
    public class Class5
    {
        public void DoSomething()
        {
        }
    }
}

This file contains two namespaces — Learn2develop.net and CoolLabs.net — each containing a class and a method.

Compile the ClassLibrary2 project so that an assembly is generated in the binDebug folder of the project — ClassLibrary2.dll.

Now, add another Class Library project to the current solution, and this time use the Visual Basic language. Name the project ClassLibrary3 (see Figure 15-23).

Figure 15-23

Figure 15.23. Figure 15-23

In the Properties page of the ClassLibrary3 project, set its root namespace to Learn2develop.net (see Figure 15-24).

Figure 15-24

Figure 15.24. Figure 15-24

In the default Class1.vb, define Class4 and add a method to it:

Public Class Class4
    Public Sub DoSomething()
    End Sub
End Class

Compile the ClassLibrary3 project so that an assembly is generated in the binDebug folder of the project — ClassLibrary3.dll.

Now add a new Windows application project (name it WindowsApp) to the current solution so that you can use the three assemblies (ClassLibrary1.dll, ClassLibrary2.dll, and ClassLibrary3.dll) that you have created.

To use the three assemblies, you need to add a reference to all of them. Because the assemblies are created in the same solution as the current Windows project, you can find them in the Projects tab of the Add Reference dialog (see Figure 15-25).

Figure 15-25

Figure 15.25. Figure 15-25

In the code-behind of the default Form1, type the Learn2develop.net namespace, and IntelliSense will show that four classes are available (see Figure 15-26).

Figure 15-26

Figure 15.26. Figure 15-26

Even though the classes are located in different assemblies, IntelliSense still finds them because all these classes are grouped within the same namespace. You can now use the classes as follows:

Learn2develop.net.Class1 c1 = new Learn2develop.net.Class1();
            c1.DoSomething();

            Learn2develop.net.Class2 c2 = new Learn2develop.net.Class2();
            c2.DoSomething();
Learn2develop.net.Class3 c3 = new Learn2develop.net.Class3();
            c3.DoSomething();

            Learn2develop.net.Class4 c4 = new Learn2develop.net.Class4();
            c4.DoSomething();

For Class5, you need to use the CoolLabs.net namespace. If you don't, IntelliSense will check against all the referenced assemblies and suggest an appropriate namespace (see Figure 15-27).

Figure 15-27

Figure 15.27. Figure 15-27

You can use Class5 as follows:

CoolLabs.net.Class5 c5 = new CoolLabs.net.Class5();
            c5.DoSomething();

To summarize, this example shows that:

  • Classes belonging to a specific namespace can be located in different assemblies.

  • An assembly can contain one or more namespaces.

  • Assemblies created using different languages are transparent to each other.

Private versus Shared Assemblies

So far, all the assemblies you have seen and created are all private assemblies — that is, they are used specifically by your application and nothing else. As private assemblies, they are stored in the same folder as your executable and that makes deployment very easy — there is no risk that someone else has another assembly that overwrites yours particular and thus breaks your application.

But assemblies can also be shared — that is, used by more than one application running on the computer. Shared assemblies are useful if they provide generic functionalities needed by most applications. To prevent DLL Hell, Microsoft has taken special care to make sure that shared assemblies are well protected. First, all shared assemblies are stored in a special location known as the Global Assembly Cache (GAC). Second, each shared assembly must have a strong name to uniquely identify itself so that no other assemblies have the same name.

A strong name comprises the following:

  • Name of the assembly

  • Version number

  • Public key

  • Culture

To deploy an assembly as a shared assembly, you need to create a signature for your assembly by performing the following steps:

  1. Generate a key pair containing a private key and a public key.

  2. Write the public key to the manifest of the assembly.

  3. Create a hash of all files belonging to the assembly.

  4. Sign the hash with the private key (the private key is not stored within the assembly).

These steps guarantee that the assembly cannot be altered in any way, ensuring that the shared assembly you are using is the authentic copy provided by the vendor. The signature can be verified using the public key.

The following sections will show you how to perform each of these steps.

For the client application using the shared assembly, the compiler writes the public key of the shared assembly to the manifest of the client so that it can unique identify the shared assembly (only the last 8 bytes of a hash of a public key are stored; this is known as the public key token and is always unique). When an application loads the shared assembly, it uses the public key stored in the shared assembly to decrypt the encrypted hash and match it against the hash of the shared assembly to ensure that the shared assembly is authentic.

Creating a Shared Assembly

You'll better understand how to create a shared assembly by actually creating one. In this example, you create a library to perform Base64 encoding and decoding. Basically, Base64 encoding is a technique to encode binary data into a text-based representation so that it can be easily transported over networks and Web Services. A common usage of Base64 is in emails.

Using Visual Studio 2008, create a new Class Library project and name it Base64Codec. In the default Class1.cs, define the Helper class containing two methods — Decode() and Encode():

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

namespace Base64Codec
{
    public class Helper
    {
        public byte[] Decode(string base64string)
        {
            byte[] binaryData;
            try
            {
                binaryData =
                    Convert.FromBase64String(base64string);
                return binaryData;
            }
catch (Exception)
            {
                return null;
            }
        }

        public string Encode(byte[] binaryData)
        {
            string base64String;
            try
            {
                base64String =
                   Convert.ToBase64String(
                   binaryData, 0, binaryData.Length);
                return base64String;
            }
            catch (Exception)
            {
                return string.Empty;
            }
        }
    }
}

Creating a Strong Name

To create a strong name for the assembly, you need to sign it. The easiest way is to use the Properties page of the project in Visual Studio 2008. Right-click on the project name in Solution Explorer, and select Properties. Select the Signing tab (see Figure 15-28), and check the Sign The Assembly checkbox. Select <New> from the Choose A Strong Name Key File dropdown list to specify a name for the strong name file.

Figure 15-28

Figure 15.28. Figure 15-28

In the Create Strong Name Key dialog (see Figure 15-29), specify a name to store the pair of keys (KeyFile.snk, for instance). You also have the option to protect the file with a password. Click OK.

An SNK file is a binary file containing the pair of public and private keys.

Figure 15-29

Figure 15.29. Figure 15-29

A strong name file is now created in your project (see Figure 15-30).

Figure 15-30

Figure 15.30. Figure 15-30

Alternatively, you can also use the command line to generate the strong name file:

sn -k KeyFile.snk

Versioning

With .NET, you can create different versions of the same assembly and share them with other applications. To specify version information, you can edit the AssemblyInfo.cs file, located under the Properties item in Solution Explorer (see Figure 15-31).

Figure 15-31

Figure 15.31. Figure 15-31

In the AssemblyInfo.cs file, locate the following lines:

...
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

The version number of an assembly is specified using the following format:

[Major Version, Minor Version, Build Number, Revision]

The AssemblyVersion attribute is used to identify the version number of an assembly. Applications that use this particular assembly reference this version number. If this version number is changed, applications using this assembly will not be able to find it and will break.

The AssemblyFileVersion attribute is used to specify the version number of the assembly, and it shows up in the properties page of the assembly (more on this in a later section).

Building the Assembly

Build the Class Library project so that Visual Studio 2008 will now generate the shared assembly and sign it with the strong name. To examine the shared assembly created, navigate to the binDebug folder of the project and type in the following command:

ildasm Base64Codec.dll

Figure 15-32 shows the public key stored in the manifest of the shared assembly.

Figure 15-32

Figure 15.32. Figure 15-32

You can obtain the public key token of the shared assembly by using the following command:

sn -T Base64Codec.dll

Figure 15-33 shows the public key token displayed in the console window. Note this number because you will use it for comparison later.

Figure 15-33

Figure 15.33. Figure 15-33

The Global Assembly Cache

Now that you have created a shared assembly, the next task is to put it into the GAC. The GAC is a central repository of .NET assemblies that can be shared by all applications. There are several reasons why you should put your shared assembly into the GAC, some of which are:

  • Security — Assemblies stored in the GAC are required to be signed with a cryptographic key. This makes it difficult for others to tamper with your assembly, such as replacing or injecting your shared assembly with malicious code.

  • Version management — Multiple versions of the same assembly can reside in the GAC so that each application can find and use the version of your assembly to which it was compiled. This helps to avoid DLL Hell, where applications compiled to different versions of your assembly can potentially break because they are all forced to use a single version of your assembly.

  • Faster loading — Assemblies are verified when they are first installed in the GAC, eliminating the need to verify an assembly each time it is loaded from the GAC. This improves the startup speed of your application if you load many shared assemblies.

The GAC is located in <windows_directory>Assembly. In most cases, it is C:WindowsAssembly. When you navigate to this folder by using Windows Explorer, the Assembly Cache Viewer launches to display the list of assemblies stored in it (see Figure 15-34).

Figure 15-34

Figure 15.34. Figure 15-34

Putting the Shared Assembly into GAC

To put the shared assembly that you have just built into the GAC, drag and drop it onto the Assembly Cache Viewer. Alternatively, you can also use the gacutil.exe utility to install the shared assembly into the GAC (see Figure 15-35):

gacutil /i Base64Codec.dll
Figure 15-35

Figure 15.35. Figure 15-35

Note

If you are using Windows Vista, make sure to run the command prompt as Administrator.

If the installation is successful, you will see the shared assembly in the Assembly Cache Viewer (see Figure 15-36).

Figure 15-36

Figure 15.36. Figure 15-36

The version number displayed next to the DLL is specified by using the AssemblyVersion attribute in the AssemblyInfo.cs file (as discussed earlier). Select the Base64Codec DLL, and click the Properties button (the button with the tick icon) to see the Properties page as shown in Figure 15-37.

Figure 15-37

Figure 15.37. Figure 15-37

The version number displayed in this page is specified using the AssemblyFileVersion attribute.

To install different versions of the same assembly to the GAC, simply modify the version number in AssemblyInfo.cs (via the AssemblyVersion attribute), recompile the assembly, and install it into the GAC.

Physically, the shared assembly is copied to a folder located under the GAC_MSIL subfolder of the GAC, in the following format:

<Windows_Directory>assemblyGAC_MSIL<Assembly_Name><Version>_<Public_Key_Token>

In this example, it is located in:

C:WindowsassemblyGAC_MSILBase64Codec1.0.0.0_2a7dec4fb0bb6

Figure 15-38 shows the physical location of the Base64Codec.dll assembly.

Figure 15-38

Figure 15.38. Figure 15-38

Making the Shared Assembly Visible in Visual Studio

By default, adding a shared assembly into the GAC does not make it appear automatically in Visual Studio's Add Reference dialog. You need to add a registry key for that to happen. Here's how to handle that.

First, launch the registry editor by typing regedit in the Run command box.

Note

If you are using Windows Vista, make sure to run regedit as Administrator.

Navigate to the HKEY_LOCAL_MACHINESOFTWAREMicrosoft.NETFrameworkAssemblyFolders key. Right-click on the AssemblyFolders key and select New

Making the Shared Assembly Visible in Visual Studio
Figure 15-39

Figure 15.39. Figure 15-39

Name the new key Base64Codec. Double-click on the key's (Default) value, and enter the full path of the shared assembly (for example, C:Documents and SettingsWei-Meng LeeMy DocumentsVisual Studio 2008ProjectsBase64CodecinDebug; see Figure 15-40).

Figure 15-40

Figure 15.40. Figure 15-40

Then restart Visual Studio 2008, and the assembly should appear in the Add Reference dialog.

Using the Shared Assembly

Let's now create a new Windows application project to use the shared assembly stored in the GAC. Name the project WinBase64.

To use the shared assembly, add a reference to the DLL. In the Add Reference dialog, select the Base64Codec assembly, as shown in Figure 15-41, and click OK.

Figure 15-41

Figure 15.41. Figure 15-41

Note in the Properties window that the Copy Local property of the Base64Codec is set to False (see Figure 15-42), indicating that the assembly is in the GAC.

Figure 15-42

Figure 15.42. Figure 15-42

Populate the default Form1 with the controls shown in Figure 15-43 (load the pictureBox1 with a JPG image).

Figure 15-43

Figure 15.43. Figure 15-43

In the code-behind of Form1, define the two helper functions as follows:

Note

Remember to import the System.IO namespace for these two helper functions.

public byte[] ImageToByteArray(Image img)
        {
            MemoryStream ms = new MemoryStream();
            img.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg);
            return ms.ToArray();
        }

        public Image ByteArrayToImage(byte[] data)
        {
            MemoryStream ms = new MemoryStream(data);
            Image img = new Bitmap(ms);
            return img;
        }

Code the Test button as follows:

private void btnTest_Click(object sender, EventArgs e)
        {
            //---create an instance of the Helper class---
            Base64Codec.Helper codec = new Base64Codec.Helper();

            //---convert the image in pictureBox1 to base64---
string base64string =
                codec.Encode(ImageToByteArray(pictureBox1.Image));

            //---decode the base64 to binary and display in pictureBox2---
            pictureBox2.Image = ByteArrayToImage(codec.Decode(base64string));
        }

Here you are creating an instance of the Helper class defined in the shared assembly. To test that the methods defined in the Helper class are working correctly, encode the image displayed in pictureBox1 to base64, decode it back to binary, and then display the image in pictureBox2.

Press F5 to test the application. When you click the Test button, an identical image should appear on the right (see Figure 15-44).

Figure 15-44

Figure 15.44. Figure 15-44

Examine the manifest of the WinBase64.exe assembly to see the reference to the Base64Codec assembly (see Figure 15-45). Observe the public key token stored in the manifest — it is the public key token of the shared assembly.

Figure 15-45

Figure 15.45. Figure 15-45

Summary

This chapter explained the parts that make up a .NET assembly. Splitting your application into multiple assemblies and modules will make your application easier to manage and update. At the same time, the CLR will only load the required assembly and modules, thereby making your application more efficient. If you have a shared assembly that can be used by other applications, consider deploying it into the Global Assembly Cache (GAC).

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.216.240.230