Customizing the Microsoft .NET Framework Common Language Runtime
|
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):
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:
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 Files\BoatRace\boats"; 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 Files\BoatRace"; } }
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. |
|