Chapter 6. Configuring Application Domains

In Chapter 5, I describe application domains as a construct used to isolate groups of assemblies that are loaded into the same process. An important aspect of this isolation is the ability for the creator of an application domain to be able to constrain various aspects of how the domain operates. For example, a domain’s creator must be able to limit the locations from which unsigned assemblies can be loaded into the domain. Other examples include the ability to control how version policy applies to assemblies in the domain and to specify the configuration file where application-specific settings can be stored. These aspects of application domain behavior, along with many others, can be customized through various settings specified when the domain is created. In this chapter, I describe how you can use these settings to customize the behavior of application domains to fit your particular scenario.

Application domain managers also play a key role in your ability to customize how application domains are created in the process. Recall from the previous chapter that an instance of your application domain manager is created by the CLR in every application domain. As part of this process, the CLR calls your application domain manager both before and after a new domain has been created to give you a chance to configure it. This detail is important because it gives you, the author of an extensible application, the ability to decide how all domains are created in the process, including those created by any add-ins you have loaded, not just the ones you create yourself. After I describe the full set of application domain configuration settings, I explain your domain manager’s role in customizing how application domains are created in a process.

Application Domain Configuration Settings

The complete list of application domain configuration settings is shown here. These settings are manipulated programmatically through public properties on the System.AppDomainSetup object.

ApplicationBase

PrivateBinPath

DisallowApplicationBaseProbing

PrivateBinPathProbe

DisallowPublisherPolicy

DisallowBindingRedirects

ConfigurationFile

LicenseFile

AppDomainInitializer

ConfigurationBytes

ShadowCopyFiles

ShadowCopyDirectories

CachePath

DynamicBase

ApplicationName

LoaderOptimization

DisallowCodeDownload

ActivationArguments

AppDomainInitializerArguments

The ConfigurationBytes "property" is actually implemented by two methods: GetConfigurationBytes and SetConfigurationBytes.

Before discussing how to use AppDomainSetup, take a look at the general categories of settings you can apply to an application domain:

  • Private assembly location settings. These settings are used to specify the root directory (and the structure of its subdirectories) in which the CLR will look for assemblies considered private to a particular application domain. The settings in this category are represented by the ApplicationBase, PrivateBinPath, DisallowApplicationBaseProbing, and PrivateBinPathProbe properties on System.AppDomainSetup.

  • Application-specific configuration settings. Applications written in managed code use configuration files to store their settings. These settings can be custom settings defined by the application, or they can be settings that are known to the CLR, such as version policy statements and remote call configuration. Configuration files are associated with an application domain using either the ConfigurationFile or the ConfigurationBytes property on System.AppDomainSetup. The ability to scope an application’s configuration data to the domain in which it is running is an essential part of the isolation provided by application domains.

  • Shadow copy settings. The CLR includes a feature called shadow copy that you can use to build applications that can be updated dynamically. Shadow copy works by leaving an assembly’s files unlocked when they are loaded from disk. In this way, the files can be replaced and reloaded without having to shut down and restart the process. The properties used to set up the shadow copy feature are ShadowCopyFiles, ShadowCopyDirectories, CachePath, and ApplicationName.

  • Assembly version binding settings. This group of settings enables you to customize how different aspects of the version policy system apply to code running in an application domain. Specifically, you can prevent from having any effect the version policy statements made either by the application or the publisher of a shared assembly. These customizations are specified using the DisallowPublisherPolicy and DisallowBindingRedirects properties on System.AppDomainSetup.

  • Miscellaneous settings. There are a few settings that don’t fit directly into the preceding categories. These settings are used to indicate whether code should be loaded into the application domain as domain neutral, to specify whether code running in the domain can dynamically download other code, and so on. The settings in this category are LicenseFile, LoaderOptimization, DynamicBase, DisallowCodeDownload, ActivationArguments, AppDomainInitializer, and AppDomainInitializerArguments.

Typically, the settings used to configure an application domain are specified when the domain is created. This is done by creating a new instance of System.AppDomainSetup, providing values for the properties you’re interested in, and passing the new instance to the System.AppDomain.CreateDomain method. For example, the following code specifies the base directory in which the CLR will search for private assemblies for the new domain:

using System;

AppDomainSetup adSetup = new AppDomainSetup();
adSetup.ApplicationBase = @"c:Program FilesMyApp";
AppDomain ad = AppDomain.CreateDomain("New Domain", null, adSetup);

Any time after the domain has been created, you can retrieve the current settings by obtaining an instance of AppDomainSetup using the SetupInformation property of System.AppDomain. For example, the following code prints the application root directory for the application domain running on the current thread:

using System;

AppDomainSetup adSetup = Thread.GetDomain().SetupInformation;

Console.WriteLine("The application base directory for the current
                   Domain is: " + adSetup.ApplicationBase);

As I explained earlier, you typically specify the configuration settings for an application domain when you create the domain. However, System.AppDomain exposes public methods that enable you to change the value of a small number of these settings after the domain has been created. If you use these methods to change domain settings, you must do so before any assemblies are loaded into the domain. Calling them later will have no effect. The settings that can be changed through properties on AppDomain, along with the corresponding properties on AppDomainSetup they affect, are shown in Table 6-1.

Table 6-1. Application Domain Settings Exposed by System.AppDomain

System.AppDomain Property

System.AppDomainSetup Equivalent

AppendPrivatePath

PrivateBinPath

ClearPrivatePath

PrivateBinPath

ClearShadowCopyPath

ShadowCopyDirectories

SetCachePath

CachePath

SetDynamicBase

DynamicBase

SetShadowCopyFiles

ShadowCopyFiles

SetShadowCopyPath

ShadowCopyDirectories

I describe how to use these methods in the following sections, where their matching AppDomainSetup property is described.

Now that I’ve provided an overview of each of the settings, let’s dig into the details of how you can use the AppDomainSetup properties to customize the application domains you create.

Private Assembly Directory Settings

If you’ve written and deployed a managed executable, you’ve probably deployed that executable and many of the assemblies it depends on to the same directory on disk. Similarly, if you’ve written a Microsoft ASP.NET application, you’ve probably deployed the code needed for your application to the bin directory under the site’s virtual directory on the Web server. By deploying applications in this way, you’re taking advantage of one of the key features of the Microsoft .NET Framework deployment model: the ability to deploy (almost) everything needed to run an application into the same directory. This deployment model has several benefits. It makes your application easier to install, uninstall, or replicate between machines. Furthermore, it enables you as an application developer to have tighter control over the dependencies that your application loads.

The key to providing this private deployment model is the ApplicationBase property on AppDomainSetup. When a host creates an application domain, this property is set to the root directory under which all dependent assemblies will be deployed. For example, the ASP.NET host sets ApplicationBase to the virtual root for the Web site. Similarly, the default CLR host sets the ApplicationBase for an executable to the directory on disk where the executable file is located. The following example sets the ApplicationBase for a new domain to a location in the Program Files directory:

using System;
AppDomainSetup adSetup = new AppDomainSetup();
adSetup.ApplicationBase = @"c:Program FilesMyApp";
AppDomain ad = AppDomain.CreateDomain("MyApp", null, adSetup);

Typically, the value of ApplicationBase is set to a fully qualified directory on the same machine on which the host is running. However, because an ApplicationBase can be any directory, you can also set it to a directory on a remote Web or file server. For example, you could set the ApplicationBase for a domain to http://www.cohowinery.com or \cohowinerymyapp. When doing so, however, keep in mind that the security requirements for code running in the application domain are greater. Specifically, code loaded from a remote location obtains a lower level of trust by default than code running from the local machine.

If you don’t provide a value for ApplicationBase when creating a new domain, the CLR will set ApplicationBase to the directory containing the executable file that caused the process to be created. For example, if you launch an executable file from c:program filesmyapp, that directory will be the default ApplicationBase for all domains created in the process.

Customizing the ApplicationBase Directory Structure

Once you’ve established the root directory for your new application domain, you can customize the subdirectories under the root in which the CLR will look for private assemblies by using the PrivateBinPath, PrivateBinPathProbe, and DisallowApplicationBaseProbing properties on AppDomainSetup.

By default, the CLR will look for dependencies directly in the ApplicationBase and in a subdirectory of the ApplicationBase that matches the simple name of the assembly you are looking for. For example, consider an application myapp.exe that depends on an assembly in the file utils.dll. Myapp.exe is deployed to c:program filesmyapp. When resolving the reference from myapp to utils, the CLR will look for the file utils.dll in the following two directories (in the order shown):

  • c:program filesmyapp

  • c:program filesmyapputils

    Note

    Note

    The CLR looks in different subdirectories for satellite resource assemblies. Satellite assemblies have a culture as part of their name. In these cases, the CLR looks for the assembly in subdirectories of the ApplicationBase named by culture instead of in the subdirectories in the preceding example. For example, if utils were a satellite resource assembly, requests for the German satellite would cause the CLR to look for utils.dll in the following subdirectories of the ApplicationBase:

    • c:program filesmyappde

    • c:program filesmyappdeutils

You can change the set of subdirectories the CLR looks in for dependencies by using the PrivateBinPath property on AppDomainSetup. PrivateBinPath is a semicolon-delimited list of subdirectories under the ApplicationBase in which you’d like the CLR to search for dependencies. Keep in mind that all directories specified using PrivateBinPath must be subdirectories of the ApplicationBase. You cannot use this property to cause assemblies to be loaded from outside of the ApplicationBase. Any subdirectories you supply using this property are searched in addition to the subdirectories the CLR would search by default. For example, the following code creates an application domain with an ApplicationBase of c:program filesmyapp and uses PrivateBinPath to add a subdirectory named bin to the list of subdirectories searched:

AppDomainSetup adSetup = new AppDomainSetup();
adSetup.ApplicationBase = @"c:Program FilesMyApp";
adSetup.PrivateBinPath = "bin";

AppDomain ad = AppDomain.CreateDomain("one", null, adSetup);

Not only does this code cause the bin subdirectory to be searched, it also causes the CLR to look in subdirectories under bin named by assembly name. That is, the CLR applies the same searching rules to the new bin subdirectory that it applies to the ApplicationBase itself. If myapp.exe were to be run in this domain, the CLR would now look in the following directories for utils.dll (assuming no culture is involved):

  • c:program filesmyapp

  • c:program filesmyapputils

  • c:program filesmyappin

  • c:program filesmyappinutils

There are two other techniques you can use to customize the layout of the directory structure under the ApplicationBase. First, you can specify additional directories to search using settings in an application configuration file. Configuration files are associated with domains by using the ConfigurationFile property on AppDomainSetup as described later in the chapter. You use the <probing/> element with the privatePath attribute to specify additional search directories. The directories you add using the configuration file are searched in addition to the directories specified using the AppDomainSetup.PrivateBinPath property. Here’s a simple example of a configuration file that uses privatePath to add the bin directory to the search list:

<configuration>
   <runtime>
      <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
         <probing privatePath="bin" />
      </assemblyBinding>
   </runtime>
</configuration>

For simple configuration files such as this one, editing the Extensible Markup Language (XML) directly using a text editor works great, but in more complex scenarios you might find it more convenient to enter your values for privatePath using the .NET Framework Configuration tool. This tool is available under the Administrative Tools group in the Microsoft Windows control panel. After adding an application to the tool, simply use its Properties dialog box to specify additional search directories. An example of an application’s Properties dialog box is shown in Figure 6-1. See the .NET Framework SDK documentation for a complete description of application configuration files and the .NET Framework Configuration tool.

Editing settings in an application’s Properties dialog box using the .NET Framework Configuration tool

Figure 6-1. Editing settings in an application’s Properties dialog box using the .NET Framework Configuration tool

The second alternative to using the PrivateBinPath property to customize the private assembly search list is to use the AppendPrivatePath and ClearPrivatePath methods on System.AppDomain. As their names imply, AppendPrivatePath is used to add additional search directories and ClearPrivatePath sets the search list back to the empty string.

Turning Off ApplicationBase Searching

In some scenarios, you might not want the CLR to look for assemblies in the ApplicationBase directory structure at all. For example, as I discuss in Chapter 8, the CLR hosting APIs enable you to customize the assembly loading process to such an extent that you can define your own assembly storage formats and locations. CLR hosts such as Microsoft SQL Server 2005 use this capability to store and load assemblies out of a relational database instead of from the file system, for example. Because SQL Server 2005 ensures that all application assemblies are loaded out of the database, it doesn’t make sense to have the CLR look for assemblies in a directory specified by ApplicationBase. Even if you do rely on the standard file system–based assembly storage, you might have a deployment model in which you want to prevent assemblies from being loaded directly out of your ApplicationBase. ASP.NET is an example of a CLR host that uses such a deployment model. If you’ve built a Web application using ASP.NET, you probably noticed that you must place the private assemblies for your application in the bin directory under the ApplicationBase—the ApplicationBase itself is never searched.

AppDomainSetup has two properties to support scenarios such as these: DisallowApplicationBaseProbing and PrivateBinPathProbe (easily the most poorly named setting of the bunch). DisallowApplicationBaseProbing is a boolean property you can use to prevent the CLR from looking for assemblies anywhere in the ApplicationBase directory structure. By setting this property to true, the CLR will not look in the ApplicationBase or in any of its subdirectories. The PrivateBinPathProbe property is similar in spirit to DisallowApplicationBaseProbing, but there is one key difference. When PrivateBinPathProbe is set, the ApplicationBase directory is never searched, but all subdirectories are. This is the property that ASP.NET uses to force you to put your private assemblies in the bin subdirectory under the ApplicationBase. PrivateBinPathProbe is not a boolean property, as you’d probably expect. (I know I did.) Instead, PrivateBinPathProbe is a string. By supplying any string whatsoever for this property, you are indicating that only the subdirectories of ApplicationBase should be searched—not the ApplicationBase itself. The following example enables this behavior by setting PrivateBinPathProbe to the string *:

AppDomainSetup adSetup = new AppDomainSetup();
adSetup.PrivateBinPathProbe = "*";

AppDomain ad = AppDomain.CreateDomain("No AppBase Domain", null, adSetup);

Configuration File Settings

The .NET Framework allows applications to store settings in XML configuration files. In many cases, these settings are known only to the applications themselves. For example, an application might wish to store a connection string that is used to connect to a database. However, as I’ve shown, these configuration files can also contain settings used to customize how the CLR behaves while running that application. In the previous section, I demonstrated how these configuration files can be used to specify an assembly search path, for example. It’s likely that you’ve run across these configuration files already as you’ve built .NET Framework applications. For example, you’ve probably edited the web.config file to customize the way your ASP.NET application works.

An application’s settings can be specified in one of a number of XML configuration files arranged in a hierarchy. At a minimum, settings can be supplied in either a configuration file that affects all applications on a machine or in a file specific to a particular application. Some scenarios support more extensive configuration systems. For example, ASP.NET allows a developer or administrator to provide settings at any level in the directory structure.

All application scenarios supported by the CLR include the notion of machine-wide configuration settings. These settings are stored in a file with a fixed name (machine.config) and location (%windir%microsoft.netframeworkversionnumber Config) so the CLR knows exactly where to find the file. In contrast, the name and location of an application-level configuration file (if one exists) must be specified when an application domain in which to run the application is created. The association between the configuration file and the application is made using the ConfigurationFile or ConfigurationBytes properties of AppDomainSetup. For example, the default CLR host supports configuration files for executable files. These configuration files must be in the same directory as the executable and must be named exe name.exe.config, where exe name is the name of the executable being launched. When the default CLR host creates the domain in which to run the executable, it sets the ConfigurationFile property to the exe name.exe.config file as shown in the following example:

AppDomainSetup adSetup = new AppDomainSetup();
adSetup.ApplicationBase = @"c:MyApp";
adSetup.ConfigurationFile = @"c:MyAppMyApp.exe.config";

AppDomain ad = AppDomain.CreateDomain("MyApp", null, adSetup);

Other application scenarios work the same way. ASP.NET uses the same technique to associate a web.config file with a domain, for example.

The ConfigurationBytes property enables you to specify your configuration file as an array of bytes instead of by supplying the name of a configuration file on disk. The byte array you pass to ConfigurationBytes is simply the contents of your XML configuration file laid out in memory. Specifying a configuration file with ConfigurationBytes is convenient for scenarios in which you generate configuration information dynamically instead of storing the data ahead of time in a disk file.

Shadow Copy Settings

One reason ASP.NET applications are easier to deploy and manage than their Active Server Pages (ASP) counterparts is because of a feature called shadow copy. Using shadow copy enables ASP.NET Web applications to be updated dynamically without shutting down and restarting the Web server. Because the application can be updated without affecting the status of the Web server, ASP.NET can support sites that must be continuously available. In this section, I describe how to configure your application domains to take advantage of shadow copy. After describing how to implement shadow copy, I walk through a sample application so you can see it in action.

Shadow copy works by making copies of the files it is requested to load. The copy is then loaded, leaving the original file untouched. By default, when an assembly file is loaded from disk, it is locked by the CLR and the Windows file system. The file cannot be replaced with a new version until the file is unloaded. The only way to unload a file in the CLR is to unload the application domain into which the file has been loaded. Unloading a domain just to unlock a file is rarely feasible in applications that must be highly available and that support a constant stream of users. As described, the shadow copy feature solves this problem by making a copy of the requested file and loading the copy, thereby leaving the original file unlocked. As a result, the original file can be replaced on the fly. It’s important to note, however, that shadow copy applies only to assemblies privately deployed with the application. Assemblies stored in the global assembly cache (GAC) are never copied using shadow copy.

Four properties on AppDomainSetup enable you to configure the shadow copy feature when creating a new domain: ShadowCopyFiles, CachePath, ApplicationName, and ShadowCopyDirectories. At a minimum, you must use ShadowCopyFiles. The other three properties are used to customize the way shadow copy works. To enable shadow copy for an application domain, complete the following steps:

  1. Turn on the shadow copy feature.

  2. Specify where the CLR should store the copied files.

  3. Specify which files are copied.

  4. Clean up the copied files if necessary.

These steps are described in detail in the sections that follow.

Turning on Shadow Copy

Shadow copy is off by default. To turn it on, you must set the value of the ShadowCopyFiles property on AppDomainSetup to any non-null string. ShadowCopyFiles is a string value and not a boolean as you’d probably expect. As a result, setting the value to any string enables shadow copy, although most implementations set the value to "true" for clarity. You can turn shadow copy on after a domain has been created by calling the AppDomain.SetShadowCopyFiles() method. The following code snippet demonstrates how to enable shadow copy using the AppDomainSetup object.

using System;
AppDomainSetup setup = new AppDomainSetup();
setup.ShadowCopyFiles = "true";
AppDomain newAD = AppDomain.CreateDomain("Shadow Copy Domain",), null, setup);

Specifying the Location for the Copied Files

As described, shadow copy works by making a copy of each file it is asked to load. As a result, after you’ve enabled shadow copy, you need to decide where you’d like the CLR to store the copied files. By default, the CLR will copy the files to a location in the downloaded files cache. This is the same cache that is used to store files that have been downloaded from another machine while running an application. If you’d rather not use the downloaded files cache, you can specify a directory in which the CLR will place the copied files by using the CachePath and ApplicationName properties on AppDomainSetup.

Note

Note

The downloaded files cache is a CLR-specific cache. It is not the same cache used by Microsoft Internet Explorer to cache Web pages, controls, and other content downloaded from Web sites.

Whether you choose to use the downloaded files cache or to specify a custom location depends on your scenario. The primary advantage of storing the copied files in the downloaded files cache is that you don’t have to worry about cleanup. The cache has a size limit that, when reached, causes the files used least recently to be deleted. As a result, you’re free to cause files to be copied into the cache and they will eventually get deleted automatically. If you specify a custom directory in which to store the files, you’re responsible for deleting them when they’re no longer used. Using the downloaded files cache does have its disadvantages, however. First, because the CLR cleans up the cache based on size quotas, you can’t control the lifetime of the copied files. If your application generates a lot of copies, you might find that the CLR tries to remove a file from the cache before you are ready for it to be removed. Second, your application must have the correct security permissions to write to the directory in which the CLR will copy the files. Specifically, the user account that your application is running under must have Write access to its user profile directory. If it doesn’t, the CLR will not be able to copy files into the downloaded files cache.

If you decide that a custom location works better for your scenario, you must set both the CachePath and ApplicationName properties on AppDomainSetup when you create a new domain. The CLR concatenates the values of these two properties to obtain the directory into which the copied files will be placed. A common way to use these two properties is to keep the value of CachePath consistent throughout your application, but provide a new value for ApplicationName for each application domain you create. In this way, all shadow copy files you create are stored under the same root directory, but subdirectories identify from which domain the files come. This makes it easy to delete the files for a particular domain when the time comes. It’s also worth noting that because you can set CachePath and ApplicationName separately for each domain, files for more than one domain can be copied by shadow copy to the same location. This is useful to reduce the number of copies made if you create multiple application domains that run the same code. For example, ASP.NET might create multiple application domains (maybe even in separate processes) to run the same Web application based on a number of scalability factors, including the number of users accessing the application. In this case, ASP.NET logically uses the name of the Web application as the value of ApplicationName so all running instances of that application share the same location for files copied by shadow copy, thereby reducing the number of copies that must be made.

Always remember that if you use CachePath and ApplicationName to specify a custom shadow copy location, you’re responsible for deleting those directories when they are no longer needed. In part because shadow copy target directories don’t map one-to-one with application domains, the CLR has no idea when it’s OK to clean up the copied files. So it’s up to you to decide when no running domains rely on a particular shadow copy location so you can delete the directory.

Specifying Which Files Are Copied

By default, all assemblies the CLR loads out of a domain’s ApplicationBase directory structure are shadow copied. This includes assemblies found by the CLR’s default loading rules (that is, those in the ApplicationBase and those in subdirectories named by version and culture) as well as any assemblies located in subdirectories specified using the PrivateBinPath property of AppDomainSetup. If you don’t want all privately located assemblies shadow copied, you can use the ShadowCopyDirectories property of AppDomainSetup to customize this behavior. ShadowCopyDirectories takes a semicolon-delimited set of subdirectories containing files that you do want to leave unlocked through shadow copy. If you specify a list of subdirectories using ShadowCopyDirectories, all files loaded out of directories not on the list, including the ApplicationBase itself, will not be copied before being loaded. For example, the following code creates an application domain such that only those files in the bin subdirectory of ApplicationBase are shadow copied:

using System;
AppDomainSetup setup = new AppDomainSetup();
setup.ShadowCopyFiles = "true";
setup.ShadowCopyDirectories = "bin";
AppDomain newAD = AppDomain.CreateDomain("Shadow Copy Domain", null, setup);

The System.AppDomain class also has a method called SetShadowCopyPath that you can use to set the list of directories copied by shadow copy after the application domain has been created.

The Shadow Copy Sample

The shadowcopy.exe sample included with this book is a Windows Forms application that shows how to use the ShadowCopyFiles, CachePath, and ApplicationName properties of AppDomainSetup to configure the shadow copy feature. Shadowcopy.exe creates secondary application domains configured for shadow copy based on the options you pick in the user interface. For example, shadowcopy.exe allows you to select either the default or a custom location in which the CLR will place the copied files. The shadowcopy.exe application is shown in Figure 6-2.

The shadowcopy.exe sample

Figure 6-2. The shadowcopy.exe sample

Shadowcopy.exe determines when to create a new application domain by using the System.IO.FileSystemWatcher class to monitor changes made to a particular assembly. Each time the file is changed, a new domain is created in which to load the new file. As domains are created, their friendly names and target copy locations are added to the table in the user interface. After you build the shadowcopy project in Microsoft Visual Studio 2005, you’ll end up with the following directory structure:

C:<project directory>inDebug
                  ShadowCopy.exe
                  CopySampleAppBase
                        CopiedFile.dll

Note that under the location into which shadowcopy.exe is built is a subdirectory called CopySampleAppBase. This directory serves as the ApplicationBase for all new domains created by shadowcopy.exe. In the CopySampleAppBase directory is a file called copiedfile.dll. This is the file containing the assembly that the FileSystemWatcher class is configured to monitor. Copiedfile.dll can be replaced in a few different ways. First, you can open a command window or use Windows Explorer simply to copy a new version of copiedfile.dll over the existing one. Remember, the existing file is left unlocked because the application domains in which it is loaded are configured for shadow copy. Alternatively, you can click the Copy Over File button in the user interface. This button is just a convenience that replaces copiedfile.dll for you so you don’t have to do it manually.

When a new domain is created based on a change to copiedfile.dll, shadowcopy.exe sets the target location for the copied files based on the values of the controls in the Shadow Copy Destination group box in the user interface. If the Default radio button is selected, the shadow-copied files will end up in the downloaded files cache. If you select the Custom radio button, you must supply values for the CachePath and ApplicationName properties by filling in the appropriate text boxes. When you run shadowcopy.exe in the Visual Studio debugger, you can use the Output window to verify that the files are being copied to the location you expect. You can tell by reading the debug output that shows the directory from which the CLR loaded copiedfile.dll. For example, if copiedfile.dll is loaded from the downloaded files cache, the Output window in the debugger will include a line such as the following:

'Domain for Copy 0': Loaded 'c:documents and settingsstevenpr
local settingsapplication dataassemblydl278kokvbt.qmqm96q78ev.m4o
7c177d0320791c6f_4a37c301copiedfile.dll', Symbols loaded.

A few interesting characteristics about this path are worth pointing out. First, the path points to a location under your user profile. As described earlier, the downloaded files cache is stored per user, so saving the files in your profile directory is a natural choice. Second, the file that has been copied (copiedfile.dll) is "hidden" several levels down in your profile’s directory structure. These extra directories are just part of the layout of the downloaded files cache. Most of this indirection is done to make the location of the files in the cache unpredictable so that files downloaded from other machines cannot easily be found on your machine.

In contrast, if you specify a custom copy location, the CLR will load copiedfile.dll from that directory. Assuming that you’ve told the CLR to save the copied files to c:program filesextappshadow401k, the debugger’s Output window will include the following line:

'Domain for Copy 0': Loaded 'c:program filesextappshadow401kassemblydl2
7c177d0320791c6f_4a37c301copiedfile.dll', Symbols loaded.

As described, each new application domain created by shadowcopy.exe is displayed in the user interface. Next to the table containing the list of domains is a button you can use to unload a particular domain from the process. When a domain is unloaded, shadowcopy.exe looks to see if a custom copy location was specified by looking at the CachePath and ApplicationName properties on the AppDomainSetup object associated with the domain. If a custom location was specified, the directory containing the copied files is deleted after the application domain has been unloaded.

To summarize, the shadowcopy.exe sample works as follows:

  1. Upon startup, shadowcopy.exe creates an instance of the System.IO.FileSystemWatcher class and initializes it to monitor changes to copiedfile.dll, which resides in the CopySampleAppBase directory under the directory from which you launch shadowcopy.exe.

  2. When copiedfile.dll is replaced, a new application domain is created in which to load the new file. The location of the copied files is determined based on the controls you’ve selected in the user interface (UI). Note that copiedfile.dll can be replaced either through the command line or shell or by using the Copy Over File button in the user interface. Once the new domain is created, and the new copiedfile.dll has been loaded into it, the friendly name of the domain is added to a list displayed in the user interface.

  3. When an application domain is unloaded by clicking the Unload Domain button, shadowcopy.exe determines whether a custom directory was specified as the location for the copied files. If so, that directory is deleted.

The code for shadowcopy.exe is shown in the following listing. I’ve deleted much of the Windows Forms boilerplate code that has more to do with laying out the user interface than with the actual functionality of the application.

Example 6-1. shadowcopy.exe Source Code

using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using System.IO;
using System.Diagnostics;
using System.Threading;
using System.Reflection;
using CopiedFile;

namespace ShadowCopy
{

   public class frmShadowCopy : System.Windows.Forms.Form
   {
      // Windows Forms locals omitted...

      private FileSystemWatcher m_watcher;
      private String m_copyAppBase;
      private int m_numCopies;

      public static frmShadowCopy ShadowCopyAppForm ;
      public static DateTime eventTime;

      // This event handler receives the "File Changed" event generated by
      // FileSystemWatcher when copiedfile.dll gets overwritten. Three events
      // get raised for every file copy so I use time to make sure I don't
      // process the "extra" events.
      private static void OnFileChanged(object source, FileSystemEventArgs e)
      {
         TimeSpan t = DateTime.Now - frmShadowCopy.eventTime;

         if (t > (new TimeSpan(TimeSpan.TicksPerSecond)))
         {
            frmShadowCopy.ShadowCopyAppForm.CreateDomainForNewFile();
         }

         frmShadowCopy.eventTime = DateTime.Now;
      }

      public frmShadowCopy()
      {

         // Required for Windows Forms Designer support
         InitializeComponent();

         // Initialize a private string to hold the name of the ApplicationBase
         // for all new domains I create.
         m_copyAppBase = AppDomain.CurrentDomain.BaseDirectory +
                                      "CopySampleAppBase";

         // Initialize the event time so we handle only one FileChange event from
         // the FileSystemWatcher.
         frmShadowCopy.eventTime = DateTime.Now;

         // I keep track of the number of copies made just as a convenience for
         // assigning friendly names to application domains and to initialize the
         // the "Application Name" field in the UI with a reasonable
         // value for the next copy.
         m_numCopies = 0;
      }

      [STAThread]
      static void Main()
      {
         frmShadowCopy frm = new frmShadowCopy();
         frmShadowCopy.ShadowCopyAppForm = frm;

         Application.Run(frm);
      }

      private void frmShadowCopy_Load(object sender, System.EventArgs e)
      {
          // Initialize the FileSystemWatcher so OnFileChanged is called each time
         // copiedfile.dll is overwritten.
         m_watcher = new FileSystemWatcher();
         m_watcher.Path = m_copyAppBase;
         m_watcher.Filter = "CopiedFile.dll";
         m_watcher.NotifyFilter = NotifyFilters.LastWrite;
         m_watcher.Changed += new FileSystemEventHandler(OnFileChanged);
         m_watcher.EnableRaisingEvents = true;

         // Initialize the CachePath and ApplicationName text boxes to point to a
         // directory under the AppBase.
         txtCachePath.Text = m_copyAppBase;
         txtAppName.Text = "Copy" + m_numCopies.ToString();
     }

     private void btnCopy_Click(object sender, System.EventArgs e)
     {
        // Copy over copiedfile.dll. This causes the FileSystemWatcher
        // to raise an event that we'll catch to begin the process
        // of creating a new application domain.
        File.Copy(AppDomain.CurrentDomain.BaseDirectory + "CopiedFile.dll",
                         m_copyAppBase + "\CopiedFile.dll", true);
     }

     // This method gets called from the OnFileChanged event handler. It
     // contains all the logic to create a new application domain
     // configured for shadow copy.
     public void CreateDomainForNewFile()
     {

        String copyLocation;

        // Create a new instance of AppDomainSetup and turn shadow copy on by
        // setting ShadowCopyFiles.
        AppDomainSetup setup = new AppDomainSetup();
        setup.ApplicationBase = m_copyAppBase;
        setup.ShadowCopyFiles = "true";

        // Determine where the files should be copied to. Initialize CachePath
        // and ApplicationName appropriately.
        if (rbCustom.Checked)
        {
           if ((txtCachePath.TextLength == 0) || (txtAppName.TextLength == 0))
           {
              MessageBox.Show("You must enter values for Cache Path
                               and Application Name when specifying
                               a custom copy location");
              return;
           }

           setup.CachePath = txtCachePath.Text;
           setup.ApplicationName = txtAppName.Text;

           copyLocation = setup.CachePath + "\" + setup.ApplicationName;
         }
         else
         {
            copyLocation = "Default";
         }

         // Create a new domain.
         AppDomain newAD = AppDomain.CreateDomain("Domain for Copy " +
                             m_numCopies.ToString(), null, setup);

         // Load the assembly into the domain. As a side effect of this
         // call to Load, copiedfile.dll will be copied to the "copied files
         // location" and will be loaded from there, thereby leaving the
         // original unlocked so it can be overwritten.
         Assembly asm = newAD.Load("CopiedFile", null);

         // Add the new domain to the list displayed in the UI.
         ListViewItem newLVI = new ListViewItem(new string[] {
                        newAD.FriendlyName , copyLocation}, -1);
         newLVI.Tag = newAD;
         listView1.Items.Add(newLVI);

         m_numCopies++;

         // Update the AppName text box so we copy to a new location next time.
         txtAppName.Text ="Copy" + m_numCopies.ToString();
      }

     private void btnUnload_Click(object sender, System.EventArgs e)
     {
        if (listView1.CheckedItems.Count == 0)
        {
           MessageBox.Show("Please select one or more Domains to unload
                            by selecting the check boxes");
           return;
        }

        foreach (ListViewItem i in listView1.CheckedItems)
        {
           AppDomain ad = (AppDomain)i.Tag;

           // If a custom shadow copy location was specified, we need to
           // delete the contents of the directory after the domain is
           // unloaded. Remember the name of the custom directory here
           // (if one was specified).
           String delDirectory = ad.SetupInformation.CachePath + "\" +
                                  ad.SetupInformation.ApplicationName;

           // Unload the domain and remove the entry from the UI.
           AppDomain.Unload(ad);
           listView1.Items.Remove(i);

           // Delete the shadow copy directory if any. We check to see
           // if it's greater than one because above we added "".
           if (delDirectory.Length > 1)
           {
                   Directory.Delete(delDirectory, true);
           }
        }
     }

   }
}

Assembly Binding Settings

When a strong-named assembly is loaded into an application domain, the default versioning rules provided by the CLR make sure that the exact version of the assembly being referenced is the one that gets loaded. This default behavior lets you enjoy the highest degree of confidence that the application as a whole will run properly because only the set of assemblies that were built and tested together will be loaded. However, there are some cases in which you might want more flexibility. So the CLR provides a version policy system that allows the application consuming the assembly, the author, or the assembly or machine administrator to change which version of an assembly gets loaded given a particular reference. By default, the version policy system is enabled in the sense that if any of these versioning rules (termed binding redirects) are in place, the CLR will use them when determining which version of an assembly to load.

Although the version policy system is beneficial for the vast majority of application scenarios, I could imagine cases in which the creator of an application domain might want to disable one or more of the available policy levels. The AppDomainSetup object includes two properties that enable you to specify such customizations. The first of these properties, DisallowBindingRedirects, causes all version policy statements made in the application configuration file to be ignored. You might find this property useful to ensure that a consistent set of assemblies is always running in your domain, for example. The second property, DisallowPublisherPolicy, causes all publisher policy statements to be ignored for shared assemblies loaded into the application domain. This property is also used in environments where the creator of the application domain wants very tight control over the versions of the assemblies that are loaded. For example, by using DisallowBindingRedirects and DisallowPublisherPolicy together, the creator of a domain can ensure that only the administrator of the machine is allowed to alter which versions of shared assemblies are loaded into the domain.

Miscellaneous Settings

There are seven properties of AppDomainSetup left to cover. These properties don’t fit directly into any of the categories described earlier and are typically used only in very specialized scenarios. It is worth describing them briefly so you know they exist should a situation arise in which you could use them.

These miscellaneous properties are LicenseFile, LoaderOptimization, DynamicBase, Disallow-Cod eDownload, ActivationArguments, AppDomainInitializer, and AppDomainInitializerArguments.

LicenseFile

The LicenseFile property was part of an early design for a licensing model for .NET Framework components. At this time, it seems this property is obsolete. However, the CLR team cannot change existing APIs for compatibility reasons, so this obsolete property is likely to stay.

LoaderOptimization

The LoaderOptimization property is used to configure whether code loaded into an application domain is done so in a domain-neutral fashion. I defer further discussion of this property to Chapter 9, where I describe the details of domain-neutral code and the various ways the CLR provides to let you configure it.

DynamicBase

Some applications generate assemblies dynamically using the classes from the System. Reflection.Emit namespace or by other means such as simply writing code out to a text file and compiling it. If the dynamically generated assemblies are private to a particular application (that is, they aren’t sharable and thus don’t have a strong name), they must be written to the domain’s ApplicationBase or a subdirectory thereof as discussed earlier in this chapter. There are scenarios, however, in which this deployment model is too restrictive. The primary reason is that the ApplicationBase directory structure must be left writable so the files can be saved there. This is not always acceptable because it allows other portions of the application to be overwritten unintentionally. An excellent example of this scenario is the ASP.NET host, which emits assemblies on the fly by compiling the code written into the Web pages that make up the application. If they were forced to write to the ApplicationBase, Web site administrators would have to leave that directory unprotected, which clearly isn’t acceptable in scenarios when an admin wants to control the contents of the application folders tightly.

The CLR solves this problem by letting the host specify an additional directory, outside of the ApplicationBase, in which dynamically generated assemblies can be stored. This directory is treated as a logical extension of the ApplicationBase in that it is searched in the same manner that the ApplicationBase directory structure is. This additional directory is specified using a combination of the DynamicBase and ApplicationName properties on AppDomainSetup. Note, however, that the CLR does not create the directory specified with DynamicBase for you. You must create it yourself before attempting to store any assemblies there.

Note

Note

If you want, you can also set the dynamic base using the SetDynamicBase method on the System.AppDomain class. If you do this, however, make sure you call SetDynamicBase before any assemblies are loaded into the application domain.

When the DynamicBase and ApplicationName properties are set, the CLR adds a directory of the form

<DynamicBase><random number><ApplicationName>

to the list of directories that are searched when resolving references to private assemblies. Notice that the name of the directory contains a random number. This random number is used to obfuscate the location of the dynamic base a bit so it’s not predictable. As a result, the host cannot determine the location in which it can write dynamic assemblies just by remembering the values it specified for DynamicBase and ApplicationName. Instead, a host must use the DynamicDirectory property on System.AppDomain after the application domain has been created to determine where to write its assemblies.

If your application is using the shadow copy feature, the CLR automatically adds the dynamic directory to the list of directories in which files are left unlocked when they are loaded. This feature is convenient in that it enables you to write over previous versions of the assemblies you generate dynamically without shutting down and restarting an application domain.

DisallowCodeDownload

The ability to download code dynamically from Web servers onto a client machine is sometimes used to provide a richer user interface for users browsing Web sites. Furthermore, code download enables new capabilities that otherwise wouldn’t be available to rich client applications, including the ability to download updates when they become available. The fact that code might get downloaded to a machine as a side effect of running an application has been popular for years. This technique lends itself quite well to client applications, but the nature of server environments makes code download less appealing for server applications. Oftentimes, servers are much more tightly controlled than client machines in part because they often support applications and services that are critical to the day-to-day running of an organization. Bringing code onto a server machine without the administrator’s knowledge goes against the preference to keep the server machine as static as possible.

The CLR supports this locked-down scenario with respect to downloaded code by allowing the creator of an application domain to disable code download completely for a particular domain. This is accomplished by setting the boolean DisallowCodeDownload property to true on the instance of AppDomainSetup that is passed to AppDomain.CreateDomain. When set, DisallowCodeDownload shuts down all avenues through which code can be downloaded onto the machine by code running in that domain. These include both the ability to download code programmatically using such methods as System.Reflection.Assembly.LoadFrom as well as using code base locations that point to Web servers in configuration files.

ActivationArguments

The .NET Framework 2.0 version of the CLR introduces a new activation model based on applications and components that are defined by an application manifest. This new activation model was initially built to support the ClickOnce deployment model. In ClickOnce, an application manifest provides enough information about the contents and security requirements of the application that the CLR deployment infrastructure can safely download and execute a rich client application over the Web without explicitly installing the application on the client machine. It’s likely that application manifests will be used in other scenarios in future versions of the Windows operating system as well. The topic of activation by a manifest is outside the scope of this book. The best place to look for information is in the .NET Framework SDK.

AppDomainInitializer and AppDomainInitializerArguments

Recall from the discussion of application domain boundaries in Chapter 5 that a good goal to have when designing an application that uses multiple application domains is to keep the communication between the various domains to a minimum. Reducing the amount of crossdomain communication helps improve performance by reducing the number of remote calls that must be made. In addition, applications that limit the number of calls between application domains often have a simpler deployment model because fewer assemblies must be deployed to a location that is visible to all application domains. The AppDomainInitializer and AppDomainInitializerArguments properties on AppDomainSetup help you build applications that minimize cross-domain communication. Oftentimes, the first thing you want to do after creating a new application domain is load an assembly into that domain, instantiate a type from that assembly, and call one of its methods. The AppDomainInitializer property enables you to implement this common design pattern with much better performance by eliminating the need for cross-domain calls. Let’s look at an example to see how this works. Say we have a human resources application that creates separate application domains in which to load objects that represent employees. Each time we create a new domain, we want to load the assembly representing the employee into that domain and initialize it. Without using the AppDomainInitializer property, we’d probably write code something like the following:

using System;
using System.Reflection;
using HumanResources;
using System.Runtime.Remoting;

namespace AppDomainInit
{
   class ADInit
   {
      [STAThread]
      static void Main(string[] args)
      {
         AppDomain ad = AppDomain.CreateDomain(
                           "AppDomainInitializer", null, null);

         ObjectHandle objHandle = ad.CreateInstance("HumanResources",
                                               "HumanResources.Employee");

         Employee e = (Employee) objHandle.Unwrap();
         e.Setup();
      }
   }
}

In this code we create a new application domain and then use the CreateInstance method on System.AppDomain to create a new instance of the Employee type. CreateInstance returns an ObjectHandle that we then unwrap and cast to a local variable of type Employee. Given an object of type Employee, we then call its Setup method to initialize the data for the employee. This code accomplishes what we want it to do—it loads a new instance of Employee into the new application domain. However, we pay the cost of several remote calls to accomplish this. Remote calls are involved both in creating the Employee instance and calling it.

The AppDomainInitializer and AppDomainInitializerArguments properties can be used to accomplish the same end result without the cost of any cross-domain calls. As part of its initialization, AppDomainInitializer takes a delegate you supply that the CLR calls from within the new application domain. AppDomainInitializerArguments is an array of strings that the CLR passes to the delegate stored in AppDomainInitializer. The preceding sample passed the name of the assembly containing the Employee type and the name of the type itself to AppDomain.CreateInstance. We can make the sample more efficient by passing these same parameters as arguments to our AppDomainInitializer. Our AppDomainInitializer can then create the Employee type from within the new application domain. The following code shows the modified sample:

using System;
using System.Reflection;
using HumanResources;
using System.Runtime.Remoting;

namespace AppDomainInit
{
   class ADInit
   {
      static void Initializer(string[] args)
      {
         Assembly hrAssembly = Assembly.Load(args[0]);
         Employee e = (Employee) hrAssembly.CreateInstance(args[1]);
         e.Setup();
      }
      [STAThread]
      static void Main(string[] args)
      {
         AppDomainSetup adSetup = new AppDomainSetup();
         adSetup.AppDomainInitializer = new
                  AppDomainInitializer(ADInit.Initializer);

         string[] initializerArgs = new string[2];
         initializerArgs[0] = "HumanResources";
         initializerArgs[1] = "HumanResources.Employee";
         adSetup.AppDomainInitializerArguments = initializerArgs;

         AppDomain ad = AppDomain.CreateDomain("AppDomainInitializer",
                                                null, adSetup);
      }
   }
}

In this example, we create a new application domain, as before, but this time we set the AppDomainInitializer property on AppDomainSetup to the method called ADInit.Initializer and set the AppDomainInitializerArguments to the assembly and the type representing an Employee. As it’s setting up the new domain, the CLR calls ADInit.Initializer, passing in the arguments we specified using AppDomainInitializerArguments. ADInit.Initializer then loads the assembly, creates the instance of Employee, and calls its Setup method. Again, the key difference in this second example is that the interaction with the Employee type is all happening from within the new application domain. In this way, no remote calls are needed to set up the Employee type in the new domain.

In some ways, the functionality provided by AppDomainInitializer and AppDomainInitializerArguments is similar to what can be accomplished using an application domain manager. As discussed in Chapter 5, an application domain manager enables you to initialize new application domains as they are created in the process. I cover this in more detail in the next section of this chapter. However, a few distinctions between these two similar approaches are worth keeping in mind. First, the application domain manager implementation doesn’t provide a way to pass arguments into the new application domain unless you are using ActivationArguments and participating in activations defined by a formal manifest. In simple scenarios such as the preceding Employee example, the mechanism for passing arguments using AppDomainInitializer is probably more straightforward. The second point to consider is that typically the implementer of the extensible application provides the application domain manager type for the process. If you are writing one of the extensions that plugs into the extensible application, you won’t be able to provide a domain manager, so the only technique available to you is the AppDomainInitializer property. As mentioned, in the next section we look in more detail at how application domain managers can be used to initialize new application domains.

Customizing Application Domain Creation Using System.AppDomainManager

Recall from Chapter 5 that an application domain manager has three primary roles. First, an application domain manager makes it easy to implement the host runtime design pattern for multidomain applications. Second, a domain manager is the means used to communicate between the managed and unmanaged portions of a CLR host. And, finally, application domain managers provide a central point from which you can customize all application domains that are created in a process. It is this last role that I discuss in this section.

Earlier in this chapter, I showed how an instance of AppDomainSetup can be passed to AppDomain.CreateDomain to customize application domains as they are being created. This approach to customizing application domains works great for the domains you create yourself, but sometimes you might want to have a say in how application domains created by others are customized. A common example of this scenario is that of an extensible application. As the author of an extensible application, you’ll likely create several application domains in which to run the add-ins authored by others. Obviously, you can customize the domains you create, but what if the add-ins create application domains of their own? There is no way to influence how those domains are created with the techniques described so far. This can be a problem if your application has the requirement to keep at least some characteristics of all domains in the process the same. For example, typically you want to enforce the same security policy on all domains in the process regardless of who created them. As the author of an extensible application, you don’t want other code in the process to be able to create application domains with more liberal security policy than you are willing to grant. Application domain managers help you solve this problem by essentially letting you intercept all calls to create an application domain within a process. The CLR calls your application domain manager at strategic points when a new application domain is being created. (See Chapter 5 for details on how to create an application domain manager.) These interception points enable you to configure all domains as you see fit, regardless of who initially called AppDomain.CreateDomain.

To understand how this works, take a look at the steps the CLR uses to involve your domain manager when a new domain is requested by a call to AppDomain.CreateDomain (see Figure 6-3):

  1. The CreateDomain method is called on your AppDomainManager to give you a chance to control the settings for the new application domain.

  2. A new instance of your AppDomainManager class is created by the CLR and loaded into the new domain.

  3. The InitializeNewDomain method is called on the new instance of AppDomainManager.

  4. The new domain manager’s ApplicationActivator property is retrieved. Providing an ApplicationActivator gives you a chance to customize how applications are activated by an application manifest.

  5. The new domain manager’s HostExecutionContextManager property is retrieved. Providing a HostExecutionContextManager enables you to configure how various context information flows across threads that run in the new application domain.

  6. The new domain manager’s HostSecurityManager property is retrieved. Providing a HostSecurityManager gives you a chance to configure security for the new application domain.

Calling an AppDomainManager when a new application domain is created

Figure 6-3. Calling an AppDomainManager when a new application domain is created

These steps are described in more detail in the following sections.

Step 1: Call AppDomainManager.CreateDomain

The System.AppDomainManager base class contains a virtual CreateDomain method that you can override in your application domain manager to hook all calls to AppDomain.CreateDomain. Whenever AppDomain.CreateDomain is called within a process, the CLR delegates the call to the application domain manager of that process by taking the parameters passed to AppDomain.CreateDomain and passing them to the CreateDomain method on the application domain manager. If you’ve associated an application domain manager with a process, as discussed in Chapter 5, your domain manager’s CreateDomain method is called. If there is no domain manager associated with the process, the implementation of CreateDomain from System.AppDomainManager itself is called.

AppDomainManager.CreateDomain has the same method signature as the most commonly used variety of AppDomain.CreateDomain as shown in the following abbreviated class definition:

public class AppDomainManager : MarshalByRefObject
{
      public virtual AppDomain CreateDomain (string friendlyName,
                                             Evidence securityInfo,
                                             AppDomainSetup appDomainInfo)
             return CreateDomainHelper(friendlyName, securityInfo,
                                       appDomainInfo);

}

By delegating all calls to create an application domain to your domain manager, the CLR provides you with a hook you can use to change any of the parameters passed to AppDomain.CreateDomain before the domain is actually created. In this way, you can customize any of the properties on AppDomainSetup or change the security evidence or the friendly name to enforce the rule that all application domains are created with properties that meet your requirements.

You might have noticed that the implementation of CreateDomain from the AppDomainManager class calls a method named CreateDomainHelper. CreateDomainHelper is a protected static method on AppDomainManager that creates an application domain. It, too, has the same parameters as AppDomain.CreateDomain. Typically, the implementation of CreateDomain in classes derived from AppDomainManager follow a two-step pattern: (1) edit any of the properties on AppDomainSetup, the security evidence, or the friendly name to fit your scenario; (2) call CreateDomainHelper, passing in your edited values. For example, in Chapter 5 I introduced a fictional CLR host that simulated a sailboat race. This application implemented an application domain manager called BoatRaceDomainManager. The add-ins to this host are boats written by third parties. Each boat is loaded into its own application domain. To illustrate how a domain manager can be used to control how domains are created, let’s impose the following constraints on all boats that are added to our application:

  1. Boats must be installed in a specific directory to be loaded.

  2. A boat cannot turn on the shadow copy feature—this could result in extra files being placed on the disk that we’re not aware of.

  3. A boat cannot specify any versioning rules that would cause the CLR to load a different version of a .NET Framework assembly than it would under the default rules.

We can meet these three requirements by changing some of the properties on the instance of AppDomainSetup that we pass on to CreateDomainHelper. We can enforce that a boat must be installed to a specific directory by setting that directory as the new domain’s ApplicationBase. If a boat is not installed in that directory, the CLR will not find the assembly containing the boat when we attempt to load it. We can enforce our last two requirements simply by setting ShadowCopyFiles to false and DisallowBindingRedirects to true. The implementation of CreateDomain from BoatRaceDomainManager is shown in the following code. As discussed, this implementation follows the typical pattern of modifying properties on the AppDomainSetup object before creating the new application domain with CreateDomainHelper.

public class BoatRaceDomainManager : AppDomainManager, IBoatRaceDomainManager
{
   public virtual AppDomain CreateDomain (string friendlyName,
                                          Evidence securityInfo,
                                          AppDomainSetup appDomainInfo)
   // Modify the ApplicationBase, ShadowCopyFiles, and
   // DisallowBindingRedirects properties.
   appDomainInfo.ApplicationBase = @"c:Program FilesBoatRaceoats";
   appDomainInfo.ShadowCopyFiles = "false";
   appDomainInfo.DisallowBindingRedirects = true;

   return CreateDomainHelper(friendlyName, securityInfo,
                             appDomainInfo);  }

Up until now, we’ve been assuming that a domain manager’s implementation of CreateDomain always creates and returns a new application domain. This is not required, however. Instead of customizing and creating a new application domain, we could take other approaches in CreateDomain. First, we could prevent the domain from being created altogether just by returning null. More commonly, we could return an application domain that already exists. In this way, we could control the number of application domains that are created and reuse them instead of creating a new application domain every time AppDomain.CreateDomain is called. This enables us to implement a load-balancing scheme for application domains, which can be useful if you’d like to restrict the number of application domains that are created for performance reasons, for example.

Step 2: Create a New Instance of the Application Domain Manager

If a new application domain is returned from your application domain manager’s implementation of CreateDomain, the CLR creates a new instance of your application domain manager and loads it into the new domain.

Step 3: Call AppDomainManager.InitializeNewDomain

After loading an instance of the application domain manager into the new domain, the CLR calls its InitializeNewDomain method. InitializeNewDomain gives you a chance to do any further application domain configuration while running within the new domain. By implementing an application domain manager, you are given two chances to customize a new application domain: one from outside the domain, and one from inside the new domain. The opportunity to customize the domain from the outside is provided when the CLR calls your application domain manager’s CreateDomain method. The opportunity to customize the new domain from inside is provided by the InitializeNewDomain method. It’s important to remember that CreateDomain and InitializeNewDomain are called on different instances of your application domain manager. (This distinction is shown in Figure 6-3.)

At first glance it might seem that CreateDomain and InitializeNewDomain are just two different ways of doing exactly the same thing. Although it is true that both enable you to customize the new domain, a few subtleties justify having both methods. First, as discussed in the previous section, an application domain manager enables you to circumvent the process of creating a new domain entirely. Instead of creating a new application domain each time, you can simply reuse an existing domain. The decision of whether to create a new domain or use an existing one obviously must be made from the outside or before the creation of the new domain begins. As a result, this type of customization can be done only within your implementation of CreateDomain.

In contrast, InitializeNewDomain is useful for doing the type of initializations that can be accomplished more efficiently from within the application domain. The classic example is loading assemblies. As discussed in Chapter 5 and earlier in this chapter, loading an assembly from within the destination domain helps you avoid the performance cost of cross-domain calls. Another important scenario in which InitializeNewDomain is useful is in the customization of the default application domain. The CLR creates the default domain internally when the process starts, so CreateDomain is not called. In this case, your only chance to customize the default domain is from within InitializeNewDomain.

InitializeNewDomain takes an instance of AppDomainSetup as shown in the following definition from the AppDomainManager base class:

public class AppDomainManager : MarshalByRefObject
{
   public virtual void InitializeNewDomain (AppDomainSetup appDomainInfo)
   {
   }
}

Although the application domain is partially constructed at the time InitializeNewDomain is called, any changes you make to the properties of the AppDomainSetup object have an effect. As described, this is the only way you can change the properties for the AppDomainSetup object belonging to the default application domain. For example, the following sample implementation of InitializeNewDomain modifies the ApplicationBase property for the default domain:

public class BoatRaceDomainManager : AppDomainManager, IBoatRaceDomainManager
{
   public virtual void InitializeNewDomain (AppDomainSetup appDomainInfo)
   {
      if (IsDefaultAppDomain())
            appDomainInfo.ApplicationBase = @"c:Program FilesBoatRace";
   }
}

Step 4: Get the ApplicationActivator

After CreateDomain and InitializeNewDomain are called, the CLR takes three objects from your application domain manager that you can provide to customize other aspects of how the new application domain will work. The CLR obtains these objects through public properties on your application domain manager class. The first of these three properties is ApplicationActivator. The ApplicationActivator property returns an object of type ApplicationActivator that you can use to customize how applications defined by manifests are activated. I don’t cover the generic topic of manifest-based activation in this book. Information about how to use a custom ApplicationActivator can be found in the .NET Framework SDK.

Step 5: Get the HostExecutionContextManager

The next object the CLR gets from your application domain manager is an object of type HostExecutionContextManager (from a property of the same name). An instance of HostExecutionContextManager enables you to control how the state related to security and call contexts flows across threads running in the application domain. Information about how to use a custom HostExecutionContextManager can be found in the .NET Framework SDK.

Step 6: Get the HostSecurityManager

The last object the CLR gets from your application domain manager is of type HostSecurityManager. The CLR accesses this object through the HostSecurityManager property. Providing an implementation of HostSecurityManager enables you to supply security evidence and to customize the code access security policy for code running in the application domain. See Chapter 10 for a discussion of HostSecurityManager.

Summary

The System.AppDomainSetup class has several properties you can use to customize the behavior of the application domains you create. Some of these properties you’ll use almost every time you create an application domain, whereas some of the properties are for situations esoteric enough that you’ll likely never use them. The most common AppDomainSetup property used is the ApplicationBase. ApplicationBase specifies a root directory that is used to find all assemblies that are private to your application domain. Application domains are customized by setting the properties on an instance of AppDomainSetup and passing that instance to AppDomain.CreateDomain.

Application domain managers also play a key role in application domain customization. The CLR calls your application domain manager implementation at various points during the creation of an application domain. These interception points give you an opportunity to set the AppDomainSetup parameters, load assemblies into the new application domain, and perform other customizations specific to your application.

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

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