Or if I had my own business, I might like to know which vendor has a ... free; others will be available through a subscr
Applied Microsoft .NET Framework Programming Jeffrey Richter PUBLISHED BY Microsoft Press A Division of Microsoft Corporation One Microsoft Way Redmond, Washington 98052-6399 Copyright © 2002 by Jeffrey Richter All rights reserved. No part of the contents of this book may be reproduced or transmitted in any form or by any means without the written permission of the publisher. Library of Congress Cataloging-in-Publication encoding="utf-8" ?> Whenever the CLR attempts to locate an assembly file, it always looks in the application’s directory first, and if it can’t find the file there, it looks in the AuxFiles subdirectory. You can specify multiple semicolon-delimited paths for the probing element’s privatePath attribute. Each path is considered relative to the application’s base directory. You can’t specify an absolute or a relative path identifying a directory that is outside the application’s base directory. The idea is that an application can control its directory and its subdirectories but has no control over other directories. By the way, you can write code that opens and parses the information contained in a configuration file. This allows your application to define settings that an administrator or a user can create and persist in the same file as all the application’s other settings. You use the classes defined in the System.Configuration namespace to manipulate a configuration file at runtime. The name and location of this XML configuration is different depending on the application type.
Probing for Assembly Files When the CLR needs to locate an assembly, it scans several subdirectories. Here is the order in which directories are probed for a culture-neutral assembly:
AppBase\AsmName.dll AppBase\AsmName\AsmName.dll AppBase\privatePath1\AsmName.dll AppBase\privatePath1\AsmName\AsmName.dll AppBase\privatePath2\AsmName.dll AppBase\privatePath2\AsmName\AsmName.dll ? In the example above, no configuration file would be needed if the JeffTypes assembly files were deployed to a subdirectory called JeffTypes since the CLR would automatically scan for a subdirectory whose name matches the name of the assembly being searched for. If the assembly can’t be found in any of the preceding subdirectories, the CLR starts all over, using an .exe extension instead of a .dll extension. If the assembly still can’t be found, a FileNotFoundException is thrown. For satellite assemblies, the same rules are followed except that the assembly is expected to be in a subdirectory of the application base directory whose name matches
the culture. For example, if AsmName.dll has a culture of “en-US” applied to it, the following directories are probed:
AppBase\en-US\AsmName.dll AppBase\en-US\AsmName\AsmName.dll AppBase\en-US\privatePath1\AsmName.dll AppBase\en-US\privatePath1\AsmName\AsmName.dll AppBase\en-US\privatePath2\AsmName.dll AppBase\en-US\privatePath2\AsmName\AsmName.dll ? Again, if the assembly can’t be found in any of the subdirectories listed here, the CLR checks the same set of assemblies looking for an .exe file instead of a .dll file.
§ §
§
For executable applications (EXEs), the configuration file must be in the application’s base directory and it must be the name of the EXE file with “.config” appended to it. For ASP.NET Web Forms and XML Web service applications, the file must be in the Web application’s virtual root directory and is always named Web.config. In addition, subdirectories can also contain their own Web.config file and the configuration settings are inherited. For example, a Web application located at http://www.Wintellect.com/Training would use the settings in the Web.config files contained in the virtual root directory and in its Training subdirectory. For assemblies containing client-side controls hosted by Microsoft Internet Explorer, the HTML page must contain a link tag whose rel attribute is set to "Configuration" and whose href attribute is set to the URL of the configuration file, which can be given any name. Here’s an example: For more information see the .NET Framework documentation.
As mentioned at the beginning of this section, configuration settings apply to a particular application and to the machine. When you install the .NET Framework, it creates a Machine.config file. There is one Machine.config file per version of the CLR you have installed on the machine. In the future, it will be possible to have multiple versions of the .NET Framework installed on a single machine simultaneously. The Machine.config file is located in the following directory:
C:\WINDOWS\Microsoft.NET\Framework\version\CONFIG Of course, C:\WINDOWS identifies your Windows directory, and version is a version number identifying a specific version of the .NET Framework. Settings in the Machine.config file override settings in an application-specific configuration file. An administrator can create a machine-wide policy by modifying a single file. Normally, administrators and users should avoid modifying the Machine.config file because this file has many settings related to various things, making it much more difficult to navigate. Plus, you want the application’s settings to be backed up and restored, and keeping an application’s settings in the application-specific configuration file enables this. Because editing an XML configuration file is a little unwieldy, Microsoft’s .NET Framework team produced a GUI tool to help. The GUI tool is implemented as a Microsoft Management Console (MMC) snap-in, which means that it isn’t available when running on a Windows 98, Windows 98 Standard Edition, or Windows Me machine. You can find the tool by opening Control Panel, selecting Administrative Tools, and then selecting the Microsoft .NET
Framework Configuration tool. In the window that appears, you can traverse the tree’s nodes until you get to the Applications node, as shown in Figure 2-6.
Figure 2-6 : Applications node of the Microsoft .NET Framework
Configuration tool From the Applications node, you can select the Add An Application To Configure link that appears in the right-hand pane. This will invoke a wizard that will prompt you for the pathname of the executable file you want to create an XML configuration file for. After you’ve added an application, you can also use this to alter its configuration file. Figure 2-7 shows the tasks that you can perform to an application.
Figure 2-7 : Configuring an application using the Microsoft .NET
Framework Configuration tool I’ll discuss configuration files a bit more in Chapter 3.
Chapter 3: Shared Assemblies Overview In Chapter 2, I talked about the steps required to build, package, and deploy an assembly. I focused on what’s called private deployment, where assemblies are placed in the application’s base directory (or a subdirectory thereof) for the application’s sole use. Deploying assemblies privately gives a company a large degree of control over the naming, versioning, and behavior of the assembly. In this chapter, I’ll concentrate on creating assemblies that multiple applications can access. The assemblies that ship with the Microsoft .NET Framework are an excellent example of globally deployed assemblies because almost all managed applications use types defined by Microsoft in the .NET Framework Class Library (FCL). As I mentioned in Chapter 2, Windows has a reputation for being unstable. The main reason for this reputation is the fact that applications are built and tested using code implemented by someone else. After all, when you write an application for Windows, your application is calling into code written by Microsoft developers. Also, a large number of companies out there make controls that application developers can incorporate into their own applications. In fact, the .NET Framework encourages this, and lot more control vendors will likely pop up over time. As time marches on, Microsoft developers and control developers modify their code: they fix bugs, add features, and so on. Eventually, the new code makes its way onto the user’s hard disk. The user’s applications that were previously installed and working fine are no longer using the same code that the applications were built and tested with. As a result, the applications’ behavior is no longer predictable, which contributes to the instability of Windows. File versioning is a very difficult problem to solve. In fact, I assert that if you take a file and change just one bit in the file from a 0 to a 1 or from a 1 to a 0, there’s absolutely no way to guarantee that code that used the original file will now work just as well if it uses the new version of the file. One of the reasons why this statement is true is that a lot of applications exploit bugs—either knowingly or unknowingly. If a later version of a file fixes a bug, the application no longer runs as expected. So here’s the problem: how do you fix bugs and add features to a file and also guarantee that you don’t break some application? I’ve given this question a lot of thought and have come to one conclusion: it’s just not possible. But, obviously, this answer isn’t good enough. Files will ship with bugs, and developers will always want to add new features. There must be a way to distribute new files with the hope that the applications will work just fine. And if the application doesn’t work fine, there has to be an easy way to restore the application to its “last-known good state.” In this chapter, I’ll explain the infrastructure that the .NET Framework has in place to deal with versioning problems. Let me warn you: what I’m about to describe is complicated. I’m going to talk about a lot of algorithms, rules, and policies that are built into the common language runtime (CLR). I’m also going to mention a lot of tools and utilities that the application developer must use. This stuff is complicated because, as I’ve mentioned, the versioning problem is difficult to address and to solve.
Two Kinds of Assemblies, Two Kinds of Deployment
The .NET Framework supports two kinds of assemblies: weakly named assemblies and strongly named assemblies. Important By the way, you won’t find the term weakly named assembly in any of the .NET Framework documentation. Why? Because I made it up. In fact, the documentation has no term to identify a weakly named assembly. I decided to coin the term so that I can talk about assemblies without any ambiguity as to what kind of assembly I’m referring to. Weakly named assemblies and strongly named assemblies are structurally identical—that is, they use the same portable executable (PE) file format, PE header, CLR header, meta, PublicKeyToken=b77a5c561934e089" "MyTypes, Version=2.0.1234.0, Culture=neutral, PublicKeyToken=b77a5c5619 34e089" "MyTypes, Version=1.0.8123.0, Culture=neutral, PublicKeyToken=b03f5f7f11d 50a3a" The first string identifies an assembly file called MyTypes.dll. The company producing the assembly is creating version 1.0.8123.0 of this assembly, and nothing in the assembly is sensitive to any one culture because Culture is set to neutral. Of course, any company could produce a MyTypes.dll assembly that is marked with a version number of 1.0.8123.0 and a neutral culture. There must be a way to distinguish this company’s assembly from another company’s assembly that happens to have the same attributes. For several reasons, Microsoft chose to use standard public/private key cryptographic technologies instead of any other unique identification technique, such as GUIDs, URLs, or URNs. Specifically, cryptographic techniques provide a way to check the integrity of the assembly’s bits as they are installed on a hard drive, and they also allow permissions to be granted on a per-publisher basis. I’ll discuss these techniques more later in this chapter. So, a company that wants to uniquely mark its assemblies must acquire a public/private key pair. Then the public key can be associated with the assembly. No two companies should have the same public/private key pair, and this distinction is what allows two companies to create assemblies that have the same name, version, and culture without causing any conflict. Note The System.Reflection.AssemblyName class is a helper class that makes it easy for you to build an assembly name and to obtain the various parts of an assembly’s name. The class offers several public instance properties, such as CultureInfo, FullName, KeyPair, Name, and Version. The class also offers a few public instance methods, such as GetPublicKey, GetPublicKeyToken, SetPublicKey, and SetPublicKeyToken. In Chapter 2, I showed you how to name an assembly file and how to apply an assembly version number and a culture. A weakly named assembly can have assembly version and culture attributes embedded in the manifest meta, PublicKeyToken=b77a5c561934e089". At this point, the CLR knows which assembly it needs. Now the CLR must locate the assembly in order to load it. When resolving a referenced type, the CLR can find the type in one of three places: § Same file Access to a type that is in the same file is determined at compile time (sometimes referred to as early bound). The type is loaded out of the file directly, and execution continues. § Different file, same assembly The runtime ensures that the file being referenced is, in fact, in the assembly’s FileRef table of the current assembly’s manifest. The runtime then looks in the directory where the assembly’s manifest file was loaded. The file is loaded, its hash value is checked to ensure the file’s integrity, the type’s member is found, and execution continues. § Different file, different assembly When a referenced type is in a different assembly’s file, the runtime loads the file that contains the referenced assembly’s manifest. If this file doesn’t contain the type, the appropriate file is loaded. The type’s member is found, and execution continues. Note The ModuleDef, ModuleRef, and FileDef meta encoding="utf-8" ?> This XML file gives a wealth of information to the CLR. Here’s what it says: § probing element Look in the application base directory’s AuxFiles and bin\subdir subdirectories when trying to find a weakly named assembly. For strongly named assemblies, the CLR looks in the GAC or in the URL specified by the codeBase element. The CLR looks in the application’s private paths for a strongly named assembly only if no codeBase element is specified. § First dependentAssembly, assemblyIdentity, and bindingRedirect elements When attempting to locate version 1.0.0.0 of the neutral culture JeffTypes
§
§
§
assembly published by the organization that controls the 32ab4ba45e0a69a1 public key token, locate version 2.0.0.0 of the same assembly instead. codeBase element When attempting to locate version 2.0.0.0 of the neutral culture JeffTypes assembly published by the organization that controls the 32ab4ba45e0a69a1 public key token, try to find it at the following URL: http://www.Wintellect.com/JeffTypes.dll. Although I didn’t mention it in Chapter 2, a codeBase element can also be used with weakly named assemblies. In this case, the assembly’s version number is ignored and should be omitted from the XML’s codeBase element. Also, the codeBase URL must refer to a directory under the application’s base directory. Second dependentAssembly, assemblyIdentity, and bindingRedirect elements When attempting to locate version 3.0.0.0 through version 3.5.0.0 inclusive of the neutral culture FredTypes assembly published by the organization that controls the 1f2e74e897abbcfe public key token, locate version 4.0.0.0 of the same assembly instead. publisherPolicy element If the organization that produces the FredTypes assembly has deployed a publisher policy file (described in the next section), the CLR should ignore this file.
When compiling a method, the CLR determines the types and members being referenced. Using this information, the runtime determines—by looking in the referencing assembly’s AssemblyRef table—what assembly was originally referenced when the calling assembly was built. The CLR then looks up the assembly in the application’s configuration file and applies any version number redirections. If the publisherPolicy element’s apply attribute is set to yes—or if the element is omitted—the CLR examines the GAC and applies any version number redirections that the publisher of the assembly feels is necessary. I’ll talk more about publisher policy in the next section. The CLR then looks up the assembly in the machine’s Machine.config file and applies any version number redirections there. Finally, the CLR knows the version of the assembly that it should load, and it attempts to load the assembly from the GAC. If the assembly isn’t in the GAC and if there is no codeBase element, the CLR probes for the assembly as I described in Chapter 2. If the configuration file that performs the last redirection also contains a codeBase element, the CLR attempts to load the assembly from the codeBase element’s specified URL. Using these configuration files, an administrator can really control what assembly the CLR decides to load. If an application is experiencing a bug, the administrator can contact the publisher of the errant assembly. The publisher can send the administrator a new assembly that the administrator can install. By default, the CLR won’t load this new assembly since the already built assemblies don’t reference the new version. However, the administrator can modify the application’s XML configuration file to instruct the CLR to load the new assembly. If the administrator wants all applications on the machine to pick up the new assembly, the administrator can modify the machine’s Machine.config file instead and the CLR will load the new assembly whenever an application refers to the old assembly. If the new assembly doesn’t fix the original bug, the administrator can delete the binding redirection lines from the configuration file and the application will behave as it did before. It’s important to note that the system allows the use of an assembly that doesn’t exactly match the assembly version recorded in the meta> Of course, a publisher can set policy only for the assemblies that it itself creates. In addition, the elements shown here are the only elements that can be specified in a publisher policy configuration file; you can’t specify the probing or publisherPolicy elements, for example. This configuration file tells the CLR to load version 2.0.0.0 of the JeffTypes assembly whenever version 1.0.0.0 of the assembly is referenced. Now you, the publisher, can create an assembly that contains this publisher policy configuration file. You create the publisher policy assembly by running AL.exe as follows:
AL.exe /out:policy.1.0.JeffTypes.dll /version:1.0.0.0
/keyfile:MyCompany.keys /linkresource:JeffTypes.config Let me explain the meaning of AL.exe’s command-line switches: § The /out switch This switch tells AL.exe to create a new PE file, called Policy.1.0.MyAsm.dll, which contains nothing but a manifest. The name of this assembly is very important. The first part of the name, Policy, tells the CLR that this assembly contains publisher policy information. The second and third parts of the name, 1.0, tell the CLR that this publisher policy assembly is for any version of the JeffTypes assembly that has a major and minor version of 1.0. Publisher policies apply to the major and minor version numbers of an assembly only; you can’t create a publisher policy that is specific to individual builds or revisions of an assembly. The fourth part of the name, JeffTypes, indicates the name of the assembly that this publisher policy corresponds to. The fifth and last part of the name, dll, is simply the extension given to the resulting assembly file. § The /version switch This switch identifies the version of the publisher policy assembly; this version number has nothing to do with the JeffTypes assembly itself. You see, publisher policy assemblies can also be versioned. Today, the publisher might create a publisher policy redirecting version 1.0.0.0 of JeffTypes to version 2.0.0.0. In the future, the publisher might want to direct version 1.0.0.0 of JeffTypes to version 2.5.0.0. The CLR uses this version number so that it knows to pick up the latest version of the publisher policy assembly. § The /keyfile switch This switch causes AL.exe to sign the publisher policy assembly using the publisher’s public/private key pair. This key pair must also match the key pair used for all versions of the JeffTypes assembly. After all, this is how the CLR knows that the same publisher created both the JeffTypes assembly and this publisher policy file. § The /linkresource switch This switch tells AL.exe that the XML configuration file is to be considered a separate file of the assembly. The resulting assembly consists of two files, both of which must be packaged and deployed to the users along with the new version of the JeffTypes assembly. By the way, you can’t use AL.exe’s /embedresource switch to embed the XML configuration file into the assembly file, making a single file assembly, because the CLR requires that the XML file be contained in its own, separate file. Once this publisher policy assembly is built, it can be packaged together with the new JeffTypes.dll assembly file and deployed to users. The publisher policy assembly must be installed into the GAC. While the JeffTypes assembly can also be installed into the GAC, it doesn’t have to be. It could be deployed into an application’s base directory or some other directory identified by a codeBase URL. Important A publisher should create a publisher policy assembly only when deploying a bug fix or a service pack version of an assembly. When installing an application “out of the box,” no publisher policy assemblies should be installed. I want to make one last point about publisher policy. Say that a publisher distributes a publisher policy assembly and for some reason the new assembly introduces more bugs than it fixes. If this happens, the administrator would like to tell the CLR to ignore the publisher policy assembly. To have the runtime do this, the administrator can edit the application’s configuration file and add the following publisherPolicy element:
This element can be placed in the application’s configuration file so that it applies to all assemblies, or it can be placed in the application’s configuration file to have it apply to a
specific assembly. When the CLR processes the application’s configuration file, it will see that the GAC shouldn’t be examined for the publisher policy assembly. So, the CLR will continue to operate using the older version of the assembly. Note, however, that the CLR will still examine and apply any policy specified in the Machine.config file. Important A publisher policy assembly is a way for a publisher to make a statement about the compatibility of different versions of an assembly. If a new version of an assembly isn’t intended to be compatible with an earlier version, the publisher shouldn’t create a publisher policy assembly. In general, use a publisher policy assembly when you build a new version of your assembly that fixes a bug. You should test the new version of the assembly for backward compatibility. On the other hand, if you’re adding new features to your assembly, you should consider the assembly to have no relationship to a previous version and you shouldn’t ship a publisher policy assembly. In addition, there’s no need to do any backward compatibility testing with such an assembly.
Repairing a Faulty Application When a console or Windows Forms application is running under a user account, the CLR keeps a record of the assemblies that the application actually loads; a record isn’t kept for ASP.NET Web Forms or XML Web services applications. This assembly load information is accumulated in memory and is written to disk when the application terminates. The files that contain this information are written to the following directory:
C:\Documents and Settings\UserName\Local Settings\ Application > —>
Part II: Working with Types and the Common Language Runtime Chapter List Chapter 4: Type Fundamentals Chapter 5: Primitive, Reference, and Value Types Chapter 6: Common Object Operations
Chapter 4: Type Fundamentals In this chapter, I’m going to introduce the information that is fundamental to working with types and the common language runtime (CLR). In particular, I’ll discuss the minimum set of behaviors that you can expect every type to have. I’ll also describe type safety and the various ways you can cast objects from one type to another. Finally, I’ll talk about namespaces and assemblies.
All Types Are Derived from System.Object The runtime requires that every object ultimately be derived from the System.Object type. This means that the following two type definitions (shown using C#) are identical:
// Implicitly derive from Object // Explicitly derive from Object class Employee { class Employee : System.Object { ? ? } } Because all object types are ultimately derived from System.Object, you are guaranteed that every object of every type has a minimum set of methods. Specifically, the System.Object class offers the public instance methods listed in Table 4-1.
Table 4-1 : Public Methods of System.Object Public Method
Description
Equals
Returns true if two objects have the same value. For more information about this method, see Chapter 6.
GetHashCode
Returns a hash code for this object’s value. A type should override this method if its objects are to be used as a key in a hash table. The method should provide a good distribution for its objects. For more information about this method, see Chapter 6.
ToString
By default, returns the full name of the type (this.GetType().FullName.ToString()). However, it is common to override this method so that it returns a String object containing a string representation of the object’s state. For example, the core types, such as Boolean and Int32, override this method to return a string representation of their values. It is also common to override this method for debugging purposes: you can call it and get a string showing the values of the object’s fields. Note that ToString is expected to be aware of the CultureInfo associated with the calling thread. Chapter 12 discusses ToString in greater detail.
GetType
Returns an instance of a Type-derived object that identifies the type of this object. The returned Type object can be used with the Reflection classes to obtain meta>" }; Int32 x;
// The following code demonstrates how strings compare // differently for different cultures. String s1 = "cotŽ", s2 = "c™te";
// Sorting strings for French in France x = new CultureInfo("fr-FR").CompareInfo.Compare(s1, s2); sb.AppendFormat("fr-FR Compare: {0} {2} {1}", s1, s2, sign[x + 1]); sb.Append(Environment.NewLine);
// Sorting strings for Japanese in Japan x = new CultureInfo("ja-JP").CompareInfo.Compare(s1, s2); sb.AppendFormat("ja-JP Compare: {0} {2} {1}", s1, s2, sign[x + 1]); sb.Append(Environment.NewLine);
// Sorting strings for the thread’s culture x = Thread.CurrentThread.CurrentCulture.CompareInfo. Compare(s1, s2); sb.AppendFormat("{0} Compare: {1} {3} {2}", Thread.CurrentThread.CurrentCulture.Name, s1, s2, sign[x + 1]); sb.Append(Environment.NewLine);
sb.Append(Environment.NewLine);
// The following code demonstrates how to use // CompareInfo.Compare’s advanced options with two Japanese // strings. These strings represent the word "shinkansen" // (the name for the Japanese high-speed train) in both // hiragana and katakana. s1 = "!?#?$?";
// ("\u3057\u3093\u304B\u3093\u305b\u3093")
s2 = "%=>=