Section 19.1. Assemblies

19.1. Assemblies

An assembly is the .NET unit of versioning and deploying code modules. An assembly consists of Portable Executable (PE) files . PE files can be either dynamic link library ( dll ) files or exe files. These PE files are in the same format as normal Windows PE files.

Assemblies contain versioning information. An assembly is the minimum unit for a single version of a piece of code. Multiple versions of the same code can run side-by-side in different applications, with no conflicts, by packaging the different versions into separate assemblies, and specifying in the configuration files which version is current.

Assemblies are self-describing because they contain metadata that fully describes the assembly and the classes, methods , and types it contains. One of the files in the assembly contains a manifest as part of the metadata, which details exactly what is in the assembly. This includes identification information (e.g., name , version), a list of the types and resources in the assembly, a map to connect public types with the implementing code, and a list of other assemblies referenced by this assembly.

A web application consists of all the files and resources in an application's virtual root directory and its subdirectories. One standard subdirectory is the bin directory, sometimes called the application assembly cache . Any assemblies placed in this directory are considered private assemblies , and are automatically made available to the application.

Another standard subdirectory is App_Code . Any source code placed in this folder is automatically compiled at runtime, and the resulting private assembly is added to the application assembly cache (though it is not physically copied to the bin directory). The physical location of the assembly files is unimportant since the CLR handles all aspects of managing these assemblies. It just works.

If an assembly file is placed in the application assembly cache, then all the classes contained in that assembly are automatically registered with the application. There is no developer or user action required for this registration to occur. Any class, method, or type defined in any assembly cache, either application or global (described below), is available to the rest of the application, subject to the access modifiers : private , protected , internal , and internal protected .

ASP.NET is configured to prohibit access to the bin and App_Code subdirectories. This prevents anyone from examining or tampering with your source code or assemblies.

Assemblies are not loaded into memory unless and until they are needed. When an assembly is needed, the CLR does not load the assembly itself into memory. If it did, that assembly would be locked until the application was stopped. This would require the application to be stopped and restarted every time a new version of the assembly was to be installed. Instead, a shadow copy of the dll is created in memory. This shadow copy is then locked, leaving the original assembly file unlocked.

The CLR constantly monitors the assembly cache to see if any new assemblies have been added or if any of the existing assemblies have changed. If a new or updated assembly is detected , the classes it contains are automatically registered with the application. In either case, all pending requests to the old version of the assembly are allowed to complete, but all new requests are handled by the new version. When the last request to the old version is finished, the shadow copy of that version is allowed to expire and the transition is complete.

19.1.1. Microsoft Intermediate Language (MSIL)

When a .NET application is compiled into an executable file, that file does not contain machine code (unless it was compiled with the Managed C++ compiler), as is the case with most other language compilers. Instead, the compiler output is a language known as Microsoft Intermediate Language (MSIL), or IL for short. When the program is run, the .NET Framework calls a Just-In-Time (JIT) compiler to compile the IL code into machine code, which is then executed. The JIT'ed machine code is cached on the server, so there is no need to recompile with every execution.

In theory, a program will produce the same IL code if written with any .NET compliant language. Though this is not always precisely true in practice, it is fair to say that for all practical purposes, all the .NET languages are equivalent.

In theory, theory and practice are the same; but in practice, they never are.

Pat Johnson, 1992

The use of IL offers several advantages. First, it allows the JIT compiler to optimize the output for the platform. As of this writing, the .NET platform is supported on Windows environments running on Intel Pentium-compatible processors, and an open source work-alike is available on some flavors of Linux. It is not a stretch to imagine the Framework being ported to other operating environments, such as other flavors of Linux or Unix, the Mac OS, or other hardware platforms. Even more likely, as new generations of processor chips become available, Microsoft could release new JIT compilers that detect the specific target processor and optimize the output accordingly .

The second major advantage of an IL architecture is that it enables the Framework to be language neutral. To a large degree, language choice is no longer dictated by the capabilities of one language over another but rather by the preferences of the developer or the team. You can even mix languages in a single application. A class written in C# can be derived from a VB2005 class, and an exception thrown in a C# method can be caught in a VB2005 method.

A third advantage is that the code can be analyzed by the CLR to determine compliance with requirements such as type safety. Things like buffer overflows and unsafe casts can be caught at compile time , greatly reducing maintenance headaches .

19.1.2. ILDASM

You can examine the contents of a compiled .NET EXE or DLL using Intermediate Language Disassembler (ILDASM) , a tool provided as part of the .NET SDK. ILDASM parses the contents of the file, displaying its contents in human-readable format. It shows the IL code, as well as namespaces, classes, methods, and interfaces.

For details on IL programming, we recommend CIL Programming: Under the Hood of .NET by Jason Bock, published by APress.


This extremely useful tool is accessible from the Start menu by clicking on Start All Programs Microsoft .NET Framework SDK Tools MSIL Disassembler. Alternatively, you can run it from a command prompt, which you can open by clicking on Start Programs Microsoft Visual Studio 2005 Visual Studio Tools Visual Studio 2005 Command Prompt. At the command prompt, enter:

 ildasm 

to open the program.

Once the program is open, click on File Open to open the file you wish to look at, or drag the file from Windows Explorer onto the ILDASM window.

Alternatively, at the command prompt enter:

 ildasm <full path>\<appname.exe> 

where the full path (optional if the exe or dll is in the current directory) and name of the exe or dll will be given as an argument. In either case, you will get something similar to that shown in Figure 19-1. You can click on the plus sign next to each node in the tree to expand that node and so drill down through the file.

Figure 19-1. ILDASM

In Figure 19-1, the file being examined is called App_Web_default.aspx.cdcab7d2.dll , which was created as part of a pre-compiled web site. You can see that it has a manifest (which will be described below), and it contains a class called _Default . The class _Default contains several properties, fields, and methods.

The icons used in ILDASM are listed in Table 19-1. Since this is a monochrome book, the colors in which the icons are displayed are mentioned.

Table 19-1. ILDASM icons

Icon

Description

Namespace (blue icon with red top edge)

Class (blue icon)

Interface (blue icon w/ yellow letter I)

Value class (brown icon)

Enum (brown icon w/ purple letter E)

Method (pink icon)

Static method (pink icon with yellow letter S)

Field (aqua icon)

Static field (aqua icon with dark blue letter S)

Event (green icon)

Property (red icon)

Manifest or class info item (red icon)


19.1.3. Manifests

Assemblies in .NET are self-describing: they contain metadata , which describes the files contained in the assembly and how they relate to each other (references to types, for example), version and security information relevant to the assembly, and dependencies on other assemblies. This metadata is contained in the assembly manifest . Each assembly must have exactly one assembly manifest.

Looking back at Figure 19-1, you can see a manifest is in the file. Double-clicking on the manifest in ILDASM will bring up a window that displays the contents of the manifest, as shown in Figure 19-2.

Looking at the manifest displayed in Figure 19-2, you can see two external assemblies are referenced: System.Web and mscorlib . Both are part of the .NET Framework. This assembly itself is referred to with the following section:

 .assembly App_Web_default.aspx.cdcab7d2 

All of these assemblies have version attributes, and the Framework assemblies also have public key token attributes. Both of these attributes will be discussed shortly.

19.1.4. Versioning

Every assembly can have a four-part version number assigned to it, of the following form:

   <major version>   .   <minor version>   .   <build number>   .   <revision number>   

Figure 19-2. Manifest

Each part of the version can have any meaning you wish to assign. There is no enforced meaning to the first number as opposed to the second, for example. The generally recommended meanings are that the major version represents a distinctly new release that may not be backward compatible with previous versions, the minor version represents a significant feature enhancement that probably is backward compatible, the build number represents a bug fix or patch level, and the revision number represents a specific compilation. Of course, your marketing department may have other ideas, and you are free to assign any versioning meaning you wish to the parts of the version number.

Though there is no enforced meaning to the four parts of the version number, the fact that they are ordered from most significant to least significant is used in the assembly binding process if you specify a range of versions to redirect.


Looking back at the manifest shown in Figure 19-2, every assembly has a version associated with it. For example, in that figure System.Web has the following version attribute:

 .ver 2:0:0:0 

This corresponds to a major version of 2 , a minor version of , a build number of , and a revision number of .

Version numbers are part of the identity of an assembly. The CLR considers two assemblies that differ only in version number to be two distinctly different assemblies. This allows for multiple versions of the same assembly to reside side by side in the same application, not to mention on the same machine.

Though it is possible to have side-by-side versions of the same assembly in the same application, this is rarely a good idea as a practical matter. You must go out of your way to make this work and it can be a maintenance headache .


As you will see shortly, the CLR differentiates between two different types of assemblies: private (those assemblies located in the application assembly cache , described above) and shared . The CLR ignores the version number of private assemblies . Adding a version number to a private assembly is a form of self-documentation, for the benefit of people examining the source code or the manifest. However, if an assembly is shared, which will be explained in detail below, the CLR will be cognizant of the version and can use it to allow or disallow the assembly to load, depending on which version is called for.

You assign versions to an assembly with assembly attributes, either at the top of your main source file or at the top of a separate source file compiled into the assembly.

Versions of Visual Studio prior to 2005 automatically included a file called AssemblyInfo.cs (or AssemblyInfo.vb ) with every web project. This file provided a convenient means of adding or modifying attributes.


Any source file that is going to include attributes must make reference to the System.Reflection namespace (unless you type in fully qualified attribute names ). In C#, include the following using statement:

 using System.Reflection; 

The attribute, or attributes, must be at the top of the source file, after the using statements but before any class definitions. In C#, it looks something like this:

 [assembly: AssemblyVersion("1.1.*")] 

Version syntax in manifests use colons to separate the numbers, and attributes in source code use periods.


The argument provided to the attribute is a string. Though the four parts of the version number have the meanings described above (major, minor, build, and revision), you can use any values you want. To the extent that the CLR checks the version number, it does not enforce any meaning other than to compare if the total version number is equal to, greater than, or less than a specified value or falls within a specified range.

That said, the Framework does impose some rules and it also provides some shortcuts for automatically generating meaningful version numbers.

  • If you specify the version, you must specify at least the major revision number. That is, specifying "1" will result in Version 1.0.0.0.

  • You can specify all four parts of the version. If you specify fewer than four parts, the remaining parts will default to zero. For example, specifying "1.2" will result in Version 1.2.0.0.

  • You can specify the major and minor numbers plus an asterisk for the build. The build will then be equal to the number of days since January 1, 2000, and the revision will be equal to the number of seconds since midnight local time, divided by 2 and truncated to the nearest integer. For example, specifying "1.2.*" will result in Version 1.2.1963.28933 if the file was compiled on May 17, 2005 at 4:04:27 P.M.

  • You can specify the major, minor, and build numbers plus an asterisk for the revision. The revision will then be equal to the number of seconds since midnight local time, divided by 2 and truncated to the nearest integer. For example, "1.2.3.*" will result in Version 1.2.3.28933 if the file was compiled at 4:04:27 P.M.

19.1.5. Private Versus Shared Assemblies

Broadly speaking, there are two types of assemblies: private and shared . A private assembly is one that is used only by a single application; a shared assembly is one which can be used by more than one application.

A private assembly is located in one of two locations. If you are using full runtime compilation, where source files are located in the App_Code directory, then the assemblies compiled from that source code and the content files will be located in a system folder somewhere on the machine, managed by the CLR. In addition, any assembly files located in the bin directory will be private assemblies.

Any public member, (such as a method, field, or property) contained in a private assembly will be available to any application in that directory by virtue of its presence in the directory. There is no need to register the assembly with the Registry, for example, as is the case with COM.

Private assemblies make no provision for versioning . The CLR does not check the version of private assemblies and cannot make load decisions based on version number. From this, it follows that it is not possible to have multiple versions of the same assembly in the same directory. However, it also follows that different directories can each have its own copy of a given assembly regardless of their respective versions. Be careful with this: it is easy to find yourself with inexplicable results when more than one version of an assembly is in use at the same time.

COM only allows a single copy of a given DLL on a machine, to be used by all the applications requiring that DLL. (Support for side-by-side COM DLLs has been added to Windows XP, but this is a relatively new feature.) Back in the days when hard disk space was a precious commodity, single copies of each DLL was a laudable, if imperfectly implemented, goal. Now, with large hard drives , it makes more sense to allow multiple copies of DLLs, one for each application that needs it. The benefit of this approach is the elimination of DLL Hell, and simplified installation and management. The deployment ramifications of private assemblies will be discussed in the section on XCOPY deployment.

DLL Hell is the following phenomenon : the user installs a new program (A) and suddenly a different program (B) stops working. As far as the user is concerned , A has nothing to do with B, but unbeknownst to the user, A and B share a DLL. Unfortunately, they require different versions of that same DLL. This problem goes away with .NET; each application can have its own private version of the DLL, or the application can specify which version of the DLL it requires.


In contrast, a shared assembly is one that can be made available to multiple applications on the machine. Typically shared assemblies are located in a special area of the drive called the Global Assembly Cache (GAC). The GAC will be discussed in more detail shortly.

Technically, you don't need to put shared assemblies in the GAC since you can specify an alternative location with a <CodeBase> element in a configuration file.

There are often reasons for creating a shared assembly other than to share an assembly between applications. For example, to take advantage of Code Access Security (CAS) , an assembly must have a strong name (described below), which effectively makes it shared.


Shared assemblies also eliminate DLL Hell because the version of the assembly is part of its identity. An application will use the version of the assembly it was originally compiled with, or the version specified by the version policy contained in a controlling configuration file.

Of course, nothing prevents a developer from releasing a new assembly with the same version numbers as a previous release. In this circumstance, you will have replaced DLL Hell with Assembly Hell.


Shared assemblies in the GAC offer some benefits over shared assemblies not in the GAC, and shared assemblies in general offer several benefits over private assemblies though they are more of a bother to prepare, install, and administer. These benefits include the following:



Performance

  • The CLR first looks for an assembly in the GAC and then in the application assembly cache.

  • Assemblies stored in the GAC do not need to have their public key signature verified every time they are loaded, but shared assemblies not in the GAC do. However, private assemblies never have their signature verified because they do not have a strong name (described below).

  • The files in a shared assembly in the GAC are verified to be present and neither tampered with nor corrupted when the assembly is installed in the GAC. For shared assemblies not in the GAC, this verification step is performed every time the assembly is loaded.



Versioning

  • Side-by-side execution, where an application, or different applications, can use different versions of the same assembly. Private assemblies in different application directories can be different versions.

  • An application will use the same version of an assembly that it was originally compiled with unless overridden by a binding policy specified in a configuration policy.

  • Applications can be redirected to use a different version of an assembly (allowing for easy updating).



Robustness

  • Files cannot be deleted except by an administrator.

  • All the files in a shared assembly are verified to be present and neither tampered with nor corrupted.

  • The shared assembly, whether in the GAC or another location, is signed with a public key to ensure that it has not been tampered with.

19.1.6. Strong Names

For an assembly to be shared, it must have a strong name. A strong name uniquely identifies a particular assembly. It is composed of a concatenation of the following:

  • The text name of the assembly (without any file extension)

  • The version

  • The culture

  • A public key token

A typical fully qualified name might look something like this:

 myAssembly,Version=1.0.0.1,Culture=en-US,PublicKeyToken=9e9ddef18d355781 

A strong name with all four parts is fully qualified , while one with fewer than all four components is partially qualified . If the culture is omitted, it can be specified as neutral . If the public key token is omitted, it can be specified as null .

The public key identifies the developer or organization responsible for the assembly. Functionally, it replaces the role of GUIDs in COM, guaranteeing the uniqueness of the name. It is the public half of a public key encryption scheme. The token listed as part of the strong name is a hash of the public key.

A public key encryption scheme, also called asymmetric encryption , relies on two numbers: a public key and a private key. They are mathematically related in such a way that if one key is used to encrypt a message, that message can only be decrypted by the other key, and vice versa. Furthermore, it is computationally infeasible, though possible, to determine one key given only the other. (Given enough time with a supercomputer, any encryption scheme can be broken.)

Many algorithms are available for calculating hashes. The only two directly supported by the .NET Framework at this time are the MD5 and SHA1 algorithms. The algorithm used for an assembly is indicated in the manifest by the key words .hash algorithm , followed by 0x00008003 for MD5 or 0x00008004 for SHA1.

The general principle is this: you generate a pair of keys, one of which you designate as private and one as public. You keep your private key very safe and very secret.

A hash code is generated for the assembly using the specified encryption algorithm, commonly SHA1. That hash code is then encrypted using RSA encryption and the private key. The encrypted hash code is embedded in the assembly manifest along with the public key. (The spaces where the encrypted hash code and the public key will go in the manifest are set to zeros before the encryption and taken into account by the encryption program.)

The CLR decrypts the hash code included in the manifest using the public key. The CLR also uses the algorithm indicated in the manifest, again typically SHA1, to hash the assembly. The decrypted hash code is compared to the just-generated hash code. If they match, the CLR can be sure the assembly has not been altered since it was signed.

19.1.6.1. Creating a strong name

There are two steps required to generate a strong name for an assembly.

The first step is to create the public/private pair of keys. The .NET Framework provides a command line tool for this purpose, sn.exe . Generate a pair of keys by executing sn with the -k option and the name of the file to hold the keys.

 sn -k KeyPair.snk 

The options passed to sn.exe are case-sensitive.


Save this file and guard it carefully if you are going to use the keys for providing proof of origin. Make a copy (on diskette or CD) and put it in a secure place, such as a safe deposit box. (If you are using the keys for testing purposes or as a guaranteed unique identifier, then there is no need for this level of security.) This file contains the private key which you should use for all the assemblies created by your organization.

In a large organization where it is not feasible for all the developers to have access to the private key, you can use a procedure known as delayed signing . This will be explained in the next section.

The second step is to compile the source code, including the key file, into an assembly. The specifics of doing so will be covered in the section on Local Deployment. For now, note the name and location of the key file for use in that process.

19.1.6.2. Delayed signing

As we mentioned, the private key must be a closely guarded item. However, this presents a quandary : access to the private key is necessary to create a strong name for an assembly. Creating a strong name is necessary to develop and test a shared assembly. Yet it may be imprudent to provide the firm's private key to all the developers working on the project who legitimately need to create strong names.

To get around this quandary, you can use delayed signing , sometimes called partial signing . In this scenario, you create the strong-named assembly using only the public key, which is safe to disseminate to anybody who wants it. You do all your development and testing. Then when you are ready to do the final build, you sign it properly with both the private and public keys.

The first step in delayed signing is to extract the public key from the key file, which contains both the private and public key. This is done from the command line using the sn tool again, passing it the -p option, the name of the key file, and the name of a file to hold the public key. In the following command, only the public key is contained in PublicKey.snk .

 sn -p KeyPair.snk PublicKey.snk 

During the publish process, described below, you can check the Delay Signing checkbox and include the key file with only the public key.



Programming ASP. NET
Programming ASP.NET 3.5
ISBN: 0596529562
EAN: 2147483647
Year: 2003
Pages: 173

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net