Customizing the Microsoft .NET Framework Common Language Runtime

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

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 doit 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.

    Категории