您的位置:首页 > 编程语言 > ASP

ASP.NET程序员应用程序域须知

2010-03-14 23:25 288 查看
本文将讨论.NET的应用程序域,并且它们是如何对ASP.NET的执行和调度产生影响的。

  当在Windows中启动Notepad程序时,众所周知程序会执行一个包含在容器内的进程。可以启动多个Notepad的实例,并且每个实例都会在一个专注的进行程运行。使用任务管理器,可以看到在系统中当前运行的所有进程的清单。

  一个进程包括可执行从操作系统中保留的在内存中的代码和程序数据。在进程之内只少有一个包含在进程之内的正在执行指令的线程,并且在多数情况下有多个线程。如果程序打开了任何文件或者资源,这些资源将属于这个进程。

  一个进程也有一个分界线。包含在进程之内的错误代码不能在当前进程之外的地区腐化。在一个进程之内很容易通讯,但是专业技术要求一个进程对另一个进程通讯是必需的。每一个进程也在一个特殊的上下文安全系统中运行,这个安全系统规定在机器和网络中进程做什么。

  一个进程是一个在Windows 操作系统中独立运行的最小单位。这会给在一个单一服务器上对一大堆应用程序的ISP提出一个问题。ISP将会分离每一个在同一个服务器上的与另一个公司的应用程序干扰的ASP.NET应用程序。但是相关的发射和执行一个对成百上千的应用程序的过程成本禁止的。

  介绍应用程序域

  .NET介绍一个应用程序域的概念,或者AppDomain像一个过程,AppDomain是既是容器又是边界线。.NET运行时间使用AppDomain作为代码和数据的容器,就像操作系统一个过程作为代码和数据的容器一样。当操作系统使用一个过程来分离不整齐的代码时,.NET运行时间使用一个AppDomain来分离在一个安全边线内的代码。

  一个AppDomain仅仅属于一个单过程,但是单个过程能够保持多重的AppDomain。一个Appdomain创建起来相对容易(与一个过程比较起来),并且与一个过程比较起来具有少的维护费用。由于这些原因,一个AppDomain是ISP(提供成千上万的应用程序)的很好的解决方案。每一个应用程序可以生存在一个独立的AppDomain之内,并且许多这样的AppDomain可以生存于一个单一的过程(节省费用)之内。

  AppDomain

  在同服务器上创建了两个ASP.NET应用程序,并且没有任何特殊配置。会发生什么事情呢?

  一个单一的ASP.NET手工进程使ASP.NET应用程序变成两方面的主要程序。在Windows XP和Windows 2000中,这一程序被命名为aspnet_wp.exe,并且这一程序运行在本地的ASPNET计数器的前后安全关系中。在Windows 2003手工程序拥有w3wp.exe并且默认运行在NETWOR SERVICE中。

  一个对旬可以进住在一个AppDomain中。每一个ASP.NET应用程序将具有它自己的一套全局变量:Cache,Application进住进同一进程,.NET AppDomain是一个独立的单元。如果存有共享的或静态成员的类,并且那些类存在于两种应用程序之内,每一个AppDomain拥有它自己的静态字段的备份—数据并不共享。每一个应用程序的数据和代码安全独立存在并且在一边界之内由AppDomain提供。

  为了在AppDomain之间通讯或者在AppDomain之间交换对象,需要查看在.NET中穿过边界的通讯技术,例如.NET细微的或Web 服务。

  对将AppDomain作为边界思想的警告之一是ASP.NET应用程序在默认情况下会带着充分的信任运行。充分信任的代码可以执行本地代码,并且本地代码可以本质地在进程之内的任何内容。需要运行带着部分信任执行应用程序来约束存取不完整的代码并且对安全的AppDomain验证所有代码。

 隐藏备份并且重新启动

  一旦一个集合加载到一个AppDomain,没有办法从AppDomain集合的办法。不过,从一个进程中移除一个AppDomain是有可能的。

  如果将一个已更新的dll复制到一个应用程序的子目录中,从ASP.NET的运行时间知道有新代码要执行。既然ASP.NET不能将dll复制到已存在的AppDomain中,它就会起动一个新AppDomain。旧的应用程序域是“排水已停止”,那就是,存在的需要被允许完成执行并且一旦它们执行完成AppDomain可以卸载。带有新代码的新的AppDomain就会开始并且开始所有的新请求。

  典型地说,当一个dll加载进一个进程时,进程对dll加锁并且不能对磁盘的上的文件进行覆盖。不过,AppDomain有一个众所周知的特点:隐藏复制那所有的允许保留在磁盘上的那些未被加锁的可替换的集合。

  运行时间对二进制子目录的带有Shadow Copy的ASP.NET进行初始化。AppDomain将任何的加锁之前的dll从二进制子目录中拷贝到一个临时位置并且再将这些dll加载到内存。Shadow Copy允许没有将网页在线的情况下对所有在二进制子目录中的任何dll进行重写。

  熟练掌握Domain

  应用程序域替换OS进程将为单独的.NET结点单元。一个可理解的应用程序域将会给你一个在ASP.NET应用程序后的手工发生的概念。使用AppDomain类的CurrentDomain属性,可以检查关于代码正在运行的AppDomain的属性,包括我们在此文章中讨论的Shadow Copy。

AppDomain

如本章前面所描述,一个AppDomain是一个进程中的独立单元。多数程序错误可以被包含在一个域中。AppDomain共享一些普通的资源,而且在这些共享组件中的错误是很少的。不论是CLR还是一个更高级的主机(例如SQL Server或者ASP.NET),主机将代表您仔细地管理AppDomain。很少需要担心自己创建或管理它们,但是必须这样做,System.AppDomain API也允许这样做。进程中的每个AppDomain可以由AppDomain类的一个实例操作。

10.2.1 创建

获取活动AppDomain引用的最简单方法是通过AppDomain.CurrentDomain静态属性。返回的对象代表了正在运行语句的AppDomain,这些语句访问这个属性。或者,可以通过调用AppDomain.CreateDomain(string friendlyName, …)方法创建一个全新的AppDomain。

CreateDomain提供多种重载,可以传递像安全Evidence、程序集和配置文件的路径(在第4章讨论过)、活动上下文以及参数等内容。AppDomainSetup类提供一个可能设置的完整列表:

public ActivationArguments ActivationArguments { get; set; }

public AppDomainInitializer AppDomainInitializer { get; set; }

public string[] AppDomainInitializerArguments { get; set; }

public string ApplicationBase { get; set; }

public string ApplicationName { get; set; }

public ApplicationTrust ApplicationTrust { get; set; }

public string CachePath { get; set; }

public string ConfigurationFile { get; set; }

public bool DisallowApplicationBaseProbing { get; set; }

public bool DisallowBindingRedirects { get; set; }

public bool DisallowCodeDownload { get; set; }

public bool DisallowPublisherPolicy { get; set; }

public string DynamicBase { get; set; }

public string LicenseFile { get; set; }

public LoaderOptimization LoaderOptimization { get; set; }

public string PrivateBinPath { get; set; }

public string PrivateBinPathProbe { get; set; }

public string ShadowCopyDirectories { get; set; }

public string ShadowCopyFiles { get; set; }

关于每项的具体内容,请参阅SDK文档。

10.2.2 卸载

为了卸载一个完整的AppDomain—— 突然中断所有进程中正在执行的代码—— 可以用static void AppDomain.Unload(AppDomain domain)方法。向这个方法传入一个活动的AppDomain作为参数,这会要求CLR卸载它以及所有和它关联的资源(如域特有的程序集)。这会在目标AppDomain中所有具有栈的线程中产生ThreadAbortException异常。

卸载一个AppDomain的基本步骤包括(1)停止所有在目标AppDomain中有栈的线程的执行,(2)异常中止这些线程,(3)引发AppDomain.Unload事件,(4)在不可达的对象上运行终结函数(如果这花费很长时间,则变为强行关机),以及(5)释放内部数据结构并垃圾回收域的全部内容。虽然这是完全依靠finally块是否会运行的主机相关性,但线程异常中止是以典型的风格发生的(也就是说,一个强行的或者友好的线程异常中止)。

10.2.3 将代码加载到AppDomain中

通过AppDomain.ExecuteAssembly*方法或者Assembly.Load*方法加载程序集时,它将与在其中加载它的AppDomain关联。共有两种类型的加载:域特有的和域中性的。程序集加载的方式将影响它在AppDomain间共享的方式,也因此影响它卸载的方式。关于域特殊性和域中性的详细内容在第4章已经讨论过。

加载域特殊性是多数程序集的默认行为。以这种方式加载的程序集与它所在的AppDomain有亲和性,并且它不被其他AppDomain所共享。造成的结果是如果一个进程中的多个AppDomain需要访问同样的程序集,它将多次加载到内存中。当一个AppDomain被卸载时,所有和它有关的域特有程序集也将被卸载,释放和它们有关的内存。遗憾的是,一个域中性程序集永远不会在包括它的进程的生命周期中被卸载,甚至是当加载它的域被卸载的时候也不会被卸载。

AppDomain.GetAssemblies实例方法返回一个Assembly[],包括每个当前在域中加载的程序集。同样地,ReflectionOnlyGetAssemblies返回一个Assembly[],包括仅在只反射的加载上下文中加载的程序集。

10.2.4 编组

在AppDomains之间共享数据时,有几种编组方法。采用哪种编组取决于所编组的数据类型。按值编组是值类型和引用类型的默认编组行为,使用System.SerializableAttribute自定义属性标记。这不会在编组时保持对象的身份,取而代之的是把每一个进入另一个AppDomain的对象视为不透明的位序列,它们在接收端反串行化。另一方面,按引用编组通过使用接收端的AppDomain中的远程代理类保存对象身份。这种类型的编组用在任何由MarshalByRefObject派生的可串行化类型中。

10.2.5 加载、卸载和异常事件

AppDomain类型提供许多事件,可以在一个AppDomain的生命周期中对重要的事件做出反应:

public event AssemblyLoadEventHandler AssemblyLoad;

public event ResolveEventHandler AssemblyResolve;

public event EventHandler DomainUnload;

public event EventHandler ProcessExit;

public event ResolveEventHandler ReflectionOnlyAssemblyResolve;

public event ResolveEventHandler ResourceResolve;

public event ResolveEventHandler TypeResolve;

public event UnhandledExceptionEventHandler UnhandledException;

当解决组件失败时会产生几个事件。它们可以用作信息目的,如记录和监控,或者插入自定义的解析行为中。它们中的每一个有一个形式为Assembly ResolveEventHandler(object sender, ResolveEventArgs e)的事件处理程序,这里地事件参数仅包括一个参数Name,String代表加载失败的组件的名字。这些事件允许返回到包含所请求内容的程序集,这些内容将被搜索到(或者在AssemblyResolve事件的情况下,直接使用这些内容)。第4章曾经讨论过如何对AssemblyResolve使用这个技术,调用自定义程序集绑定行为。

同时还有一些纯信息的事件。例如,AssemblyLoad事件在程序集加载到AppDomain中时触发。同样地,DomainUnload在AppDomain卸载时触发,ProcessExit在包括的进程退出时被调用。

有一个基于异常的事件:UnhandledException,如果一个异常没有被处理而它在AppDomain的调用栈中已处于顶部,那么这个事件就会被触发。它的事件处理程序是void UnhandledExceptionEventHandler(object sender, UnhandledExceptionEventArgs e)。事件参数由两个部分组成:ExceptionObject和IsTerminating,指明未被处理的异常是否已经导致CLR终止。

10.2.6 AppDomain孤立性

已经在本章接触了许多AppDomain的知识。在第4章有更加详细的相关讨论,将它们和程序集加载进程联系在一起进行考虑。因为AppDomain和程序集的联系是非常紧密的,将继续密切介绍如何用AppDomain(在某些情况应该)将程序集彼此独立。

想象一种情况,一个主机应用程序需要在第三方插件中执行代码。插件是一个现存应用程序的标准扩展,用户可以使用原始应用程序所没有的特定的或者增强的特性。这对于ISV应用程序是非常普通的模型,.NET Framework的体系结构使其变得非常简单。程序集是封装插件的完美的部署单元。

AppDomain提供了很多运行在其他AppDomain上代码之间的孤立性。这个屏障在恶意代码或可能造成破坏的偶然代码和主机应用程序之间创建了一层保护层。静态状态不会受影响,安全权限可以显式降级(例如,如果要拒绝访问插件以访问磁盘),插件的崩溃也不会使主机应用程序崩溃,并且可以以控制的方式处理它。

加载由一个标准基类派生而来的插件的代码如程序清单10-2所示。

程序清单10-2 用AppDomain实现插件孤立性

void MainPluginLoop()

{

List<AppDomain> plugins = new List<AppDomain>();

// Load our plugins

plugins.Add(LoadAndExecutePlugin("PluginA.dll", "FooPlugin"));

/* Do some interesting work */

// To unload plugins already loaded, this code does the trick

foreach (AppDomain ad in plugins)

AppDomain.Unload(ad);

}

AppDomain LoadAndExecutePlugin(string pluginBin, string pluginType)

{

// Create a new AppDomain and assign it restricted permissions

bool failedToLoad = false;

AppDomain ad = AppDomain.CreateDomain("PluginIsolation!" + pluginBin);

ApplyPluginPolicy(ad);

// Load & execute the plugin

PluginBase plugin = null;

try

{

plugin = ad.CreateInstanceAndUnwrap(pluginBin, pluginType)as PluginBase;

}

catch (Exception)

{

// This code should actually tell the host why it failed

}

if (plugin == null)

{

// Unload the new AppDomain & return null to indicate failure

AppDomain.Unload(ad);

ad = null;

}

else

{

plugin.Run();

}

// Return the new AppDomain so the host can track it

return ad;

}

void ApplyPluginPolicy(AppDomain ad)

{

// Enable plugins to execute code

PermissionSet rootPermissions = new PermissionSet(PermissionState.None);

rootPermissions.AddPermission(

new SecurityPermission(SecurityPermissionFlag.Execution));

UnionCodeGroup rootGroup = new UnionCodeGroup(

new AllMembershipCondition(), new PolicyStatement(rootPermissions));

// Now locate the permissions for the "Internet" zone

NamedPermissionSet internet = null;

IEnumerator policyEnum = SecurityManager.PolicyHierarchy();

while (policyEnum.MoveNext())

{

PolicyLevel level = (PolicyLevel)policyEnum.Current;

if (level.Type.Equals(PolicyLevelType.Machine))

{

foreach(NamedPermissionSet permission inlevel.NamedPermissionSets)

{

if (permission.Name.Equals("Internet"))

{

internet = permission;

break;

}

}

if (internet != null)

break;

}

}

// Use those as the basis for plug-in CAS rights

UnionCodeGroup internetGroup = new UnionCodeGroup(

new ZoneMembershipCondition(SecurityZone.MyComputer),

new PolicyStatement(internet));

internetGroup.Name = "PluginInternet";

rootGroup.AddChild(internetGroup);

// Now just set the level on the AppDomain

PolicyLevel adLevel = PolicyLevel.CreateAppDomainLevel();

adLevel.RootCodeGroup = rootGroup;

ad.SetAppDomainPolicy(adLevel);

}

LoadAndExecutePlugin获得二进制名称以及加载的插件的类型名称作为输入。它建立了一个新的AppDomain并使用CreateInstanceAndUnwrap方法创建了一个插件类型的实例。插件类型必须继承于基类PluginBase:

public abstract class PluginBase : MarshalByRefObject

{

public abstract void Run();

/* Some interesting general plug-in methods go here */

}

注意,PluginBase继承MarshalByRefObject,它阻止插件程序集无意中被加载到主机的AppDomain中。这种方法并不好,因为程序集或者模块初始程序将在主机AppDomain中运行,形成了可能的安全漏洞。

ApplyPluginPolicy代码使用System.Security、System.Security.Permissions和System.Security.Policy命名空间中许多可用的基础结构生成为AppDomain设置的合适代码访问安全(CAS)权限集。它限制在其中加载的代码不能执行需要特权的操作。在第9章曾经详细讨论过CAS。最终的结果是插件只能执行授权给Internet应用程序的操作。这不是非常多的工作,并且可以确定不允许插件接触磁盘。

注意,隔离每个AppDomain中的插件并不总是必须的。尤其是当考虑到可能需要加载许多域专用的代码到多个插件中时,会造成一些重复的代码加载并对工作集造成影响。一个合理的妥协方法是隔离单个AppDomain中所有的插件或者使用插件AppDomain池。这就是说插件可以互相位于彼此之上,但是它至少会保护主机应用程序不被破坏。最终,这是一个必须达成的设计权衡。

1. AppDomain本地存储(AppDomain-Local Storage,简称ALS)

直到使用一些类似于线程静态字段的内容,每个AppDomain才会包含一个所有静态字段的副本。所有类(或静态)构造函数将在一个给定的AppDomain中只运行一次。这意味着如果在不同的AppDomain中加载了相同的程序集,每个程序集将运行类的构造函数,并且每个程序集将分别包含所有静态字段的独立值。

和线程本地存储(TLS)非常相似,也可以存储同一个AppDomain相关的任意类型数据,而不必使用静态字段。这个功能并不是和Framework提供的TLS支持一样高级,因为ALS提供一个命名的数据存储槽的字典。和TLS一样,任何存储在AppDomain中的数据对该AppDomain的外部都不可见。为了使用ALS,只要使用AppDomain实例方法object GetData(string name)和void SetData(string name, object data)。其中第一个是检索与name相关的数据,如果底层键为空,则检索与null关联的数据。SetData创建或者改变name和data之间现存的关联。

本文为百度博客中的转贴转发而来。希望对和我一样正在学习的朋友有所帮助。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: