Expert WSS 3.0 and MOSS 2007 Programming 读书笔记(一)

本文深入探讨了SharePoint2007的架构,包括ASP.NET托管环境、IIS功能增强、SharePoint扩展机制等内容。重点介绍了HTTP运行时管道、虚拟路径提供者、HTTP模块及处理器工厂等工作原理。

第一章        The SharePoint 2007 Architecture

 

1.ASP.NET Hosting Environment


  ASP.NET能够被托管到不同的环境中,比如,IIS 5.0, IIS 6.0, IIS 7.0,也可以托管到一个定制的受管制的应用程序中,比如控制台程序。把ASP.NET 托管到一个特定的环境中,需要两个组件。


  Worker request class,这个类是从HttpWorkerRequest抽象基类继承而来,所有的Worker request class 都需要实现HttpWorkerRequest API,这些API把HttpRuntime和底层通信的具体环境细节隔离开来,这样,任何一个实现了HttpWorkerRequest API的运行环境都可以托管 ASP.NET。


  Runtime class.对于每一个客户端的请求,Runtime Class都必须完成两个任务。

  1. 实例化和初始化对应的worker request class。

  2. 调用对应的HttpRuntime的ProcessRequest方法,传递worker request class实例完成请求。


public abstract class HttpWorkerRequest

{

  public virtual void CloseConnection();

  public abstract void EndOfRequest();

  public abstract void FlushResponse(bool finalFlush);

  public virtual byte[] GetPreloadedEntityBody();

  public virtual int GetPreloadedEntityBody(byte[] buffer, int offset);

  public abstract string GetQueryString();

  public virtual int ReadEntityBody(byte[] buffer, int size);

  public abstract void SendResponseFromFile(string filename, long offset,long length);

  public abstract void SendResponseFromMemory(byte[] data, int length);

}

  如上所示,HttpRuntime调用上面的HttpWorkerRequest的CloseConnection等函数时,不需要知道底层的运行环境是IIS5.0,IIS6.0还是IIS7.0。因为ASP.NET实现了标准的IIS5.0,IIS6.0以及IIS7.0版本的HttpWorkerRequest API。对于定制的受管制的应用程序,比如控制台程序,ASP.NET亦实现了标准的SimpleWorkerRequest API。


  ASP.NET 2.0框架自带了两个重要的Runtime Classses,分别是ISPAIRuntime和PipelineRuntime。

ISPAIRuntime,适用于运行在ISAPI mode下边的IIS5.0,IIS6.0以及IIS7.0程序。

PipelineRuntime,只适用于运行在integrated mode边的IIS7.0程序。


  由于表述了不同的环境,这些运行时类各自差异很大。但是它们都实现了两个一样的方法,分别是:

  ProcessRequest used for ISAPIRuntim。

  GetExecuteDelegate used for PipelineRuntime。


  每个运行时在处理用户请求时都有两个重要的步骤:首先,实例化并且初始化一个worker request class。然后,把这个实例传递给一个对应的HttpRuntime方法处理该请求。

 

2. Internet Information Services ( IIS )


  从IIS6.0开始,用户可以开发自己的ISAPI扩展和过滤模块来扩展服务器的功能。IIS通过下面的函数来和扩展模块通信。

DWORD WINAPI HttpExtensionProc (LPEXTENSION_CONTROL_BLOCK lpECB);

参数lpECB引用了和当前请求相关的Extension_Control_Block数据结构。


  每一个ISAPI扩展模块都是为处理某个特定的资源而设计的,这些资源以特定的文件名后缀作区分。比如asp.dll处理所有以asp为后缀的页面。在安装的时候,ASP.NET自动把.aspx, .asmx, .asax, .ashx, 都注册到IIS的metabase中以处理相应的资源请求。

 

  从IIS6.0开始,用户可以把web应用程序集结到应用程序池中,同一个应用程序池中的web应用程序分享同一个worker process,工作者进程是w3wp.exe的一个实例。


  应用程序池的引入增加了服务器的可靠性和稳定性。Web应用程序不再运行在IIS进程内,所以,Web应用程序的不良行为将不会对服务器产生直接影响。由于每个应用程序池都是以进程为界定的,所以,应用程序池各自之间也互不影响。对某个特定的应用程序池进行升级或者排除错误时,都不需要重新启动整个服务器,这为管理员的工作带来很大便利。


  在IIS 6.0中还引入了一个新的kernel-mode组件: http.sys。HTTP协议栈避免了worker process和IIS process之间的通信。早期的IIS是通过Windows Socket API (WinSock)来接收客户端的http请求以及发送反馈,WinSock运行在user-mode下面。

 

  IIS6.0把每一个应用程序池下面的新增虚拟目录都注册到http.sys驱动程序,http.sys的主要功能是监听所有的http请求,并把它传递给对应的应用程序池的worker process。在http.sys里为每一个应用程序池都实现了一个相应的队列,所有的http请求都被依次排入队列,worker process只需要直接从相应队列中读取相应请求即可。这种机制避免了worker process和IIS process之间的通信。减少了系统的资源消耗,提升了性能。


  这种机制同进也改善了系统的稳定性:当某个worker process的工作出现问题时,http.sys仍然继续工作,并且接收相应的客户端的http请求排入队列,等到服务器重新启动一个新的worker process来处理队列中的请求,用户请求会因为故障而延时,但不会被拒绝。


  额外的系统性能提升是:http.sys实现了一个kernel – mode缓存,同样的请求可以直接从kernel – mode缓存读出。

 

3. SharePoint Extensions

 

SP WebService


SPWebService是SPWebApplication的容器。

 

SPApplicationPool


SPWebServiceCollection wsc = new SPWebServiceCollection(SPFarm.Local);

foreach (SPWebService ws in wsc)

{

  SPApplicationPoolCollection apc = ws.ApplicationPools;

  foreach (SPApplicationPool ap in apc)

  {

    Response.Write(ap.Name);

    Response.Write(“ < /br > ”);

  }

}

 

SPIisWebSite


HttpContext context = HttpContext.Current;

int instanceId = int.Parse(context.Request.ServerVariables[“INSTANCE_ID”],

NumberFormatInfo.InvariantInfo);

SPIisWebSite site = new SPIisWebSite(instanceId);

site.ServerComment = “My “ + site.ServerComment;

site.Update();

 

SPWebApplication


SPWebServiceCollection wsc = new SPWebServiceCollection(SPFarm.Local);

foreach (SPWebService ws in wsc)

{

  SPWebApplicationCollection wac = ws.WebApplications;

  foreach (SPWebApplication wa in wac)

  {

    Response.Write(wa.Name);

    Response.Write(“ < br/ > ”);

       }

}

 

SP WebApplicationBuilder


SPWebApplicationBuilder类的实例可以用来创建SPWebApplication类。

SPWebApplicationBuilder wab = new SPWebApplicationBuilder(SPFarm.Local);

wab.Port = 12000;

SPWebApplication wa = wab.Create();

wa.Provision();

 

ISAPI Runtime


public int ProcessRequest(IntPtr ecb, int iWRType)

{

  HttpWorkerRequest request = CreateWorkerRequest(ecb, iWRType);

  HttpRuntime.ProcessRequest(request);

  return 0;

}

 

ASP.NET HTTP Runtime Pipeline


public static void ProcessRequest(HttpWorkerRequest wr)

{

  HttpContext context1 = new HttpContext(wr, true);

  IHttpHandler handler1 =

  HttpApplicationFactory.GetApplicationInstance(context1);

  if (handler1 is IHttpAsyncHandler)

  {

    IHttpAsyncHandler handler2 = (IHttpAsyncHandler)handler1;

    context1.AsyncAppHandler = handler2;

    handler2.BeginProcessRequest(context1, _handlerCompletionCallback,context1);

  }

  else

  {

    handler1.ProcessRequest(context1);

    FinishRequest(context1.WorkerRequest, context1, null);

  }

}

  http请求总是被异步处理,所以上面程序中只会调用BeginProcessRequest。

 

  HttpRuntime, HttpApplicationFactory, and HttpApplication以及后面要讨论的其它一些组件一起构成了ASP.NET HTTP 运行时管道。管道中的每一个组件都会从前一个组件中获取一个HttpContext对象,从对象中抽取所需要处理的信息,并把处理完的信息存回到对象中,最后把对象传递给下一个组件。每一次http请求都会由不同的管道来处理,同一个管道不可以被两次请求共享。

每个应用程序域都有一个HttpApplicationFactory对象实例, 这个对象的GetApplicationInstance方法可以用来获取当前应用程序的HttpApplicationFactory对象实例,这个实例在类的内部存储在_theApplicationFactory中。

 

global.asax

 

  global.asax是可选的,它必须被放置在应用程序的根目录下,放置在其它子目录下的global.asax文件将被忽略。

  如果当前应用程序的根目录下没有global.asax文件,GetApplicationInstance函数将会返回一个ASP.NET类HttpApplication的 一个实例来代表当前应用程序。

 

  如果根目录下面存在global.asax文件,GetApplicationInstance函数将会返回一个HttpApplication子类的实例来代表当前应用程序。

  这个HttpApplication派生的子类并不是一个ASP.NET框架的标准类。它是根据global.asax文件内容由GetApplicationInstance动态生成的,并且动态编译成组件。动态生成和编译只在首次访问时进行一次,只有当global.asax文件的内容或者时间戳发生改变时才会再次进行。

 

public class HttpApplicationFactory

{

  private Stack _freeList;

  private int _numFreeAppInstances;

  internal static IHttpHandler GetApplicationInstance(HttpContext context)

  {

    GenerateAndCompileAppClassIfNecessary(context);

    HttpApplication appInstance = null;

    lock (_freeList)

    {

      if (_numFreeAppInstances > 0)

      {

        appInstance = (HttpApplication)_freeList.Pop();

        _numFreeAppInstances--;

      }

    }

    if (appInstance == null)

    {

      appInstance = InstantiateAppInstance();

      appInstance.InitializeAppInstance();

    }

    return appInstance;

  }

}

 

HttpApplication


  每个HTTP模块都是一个实现了IHttpModule接口的类。InstantiateAppInstance方法首先读取配置文件中的< httpModules >单元,每个单元都会有一些< add >子元素。每个<add>子元素都两个重要的属性:名字和类型。

public interface IHttpModule

{

  void Dispose();

  void Init(HttpApplication context);

}

  InitializeAppInstance方法首先读取每个<add>子元素的type属性和.Net refecltion来动态地实例化相应的HTTP模块。然后再调用HTTP模块的Init方法来初始化模块(参见上面接口的代码)。Init方法的主要功能是注册HttpApplication对象的事件处理句柄。

InitializeAppInstance方法然后实例化ApplicationStepManager的一个实例,HttpApplication对于请求的处理是由许多个有序的执行步骤组成的。每一个执行步骤都是一个IExecutionStep类型的对象,IExecutionStep总是和某个HttpApplication事件相关联,并且实现Execute方法来执行这些步骤。


  ApplicationStepManager有两个方法:BuildSteps和ExecuteStage,前者负责IExecutionStep对象的生成,后者调用IExecutionStep的Execute方法完成对象的执行。在实例化一个ApplicationStepManager对象以后,InitializeAppInstanced 调用其BuildSteps依序生成相关事件的IExecutionStep对象,并把这些对象依序加入一个内部的集合中。


  在ProcessRequest方法中调用的BeginProcessRequest方法,会在内部遍历这个集合,按照加入的顺序读出IExecutionStep对象,并且最终调用ApplicationStepManager的ExecuteStage方法来执行这些IExecutionStep对象。

 

SPHttpApplication


  SPHttpApplication类主要重载了HttpApplication基类的GetVaryByCustomString和Init方法,并且提供了两个公共的方法:RegisterGetVaryByCustomStringHandler和DeregisterGetVaryByCustomStringHandler。


public sealed override string GetVaryByCustomString(HttpContext context, string custom)

{

  StringBuilder stringBuilder = new StringBuilder();

  readerWriterLock.AcquireReaderLock(-1);

  try

  {

    foreach (IVaryByCustomHandler  varyByCustomHandler  in varyByCustomHandlers)

    {

      stringBuilder.Append(varyByCustomHandler.GetVaryByCustomString(this, context, custom));

    }

  }

  finally

  {

    readerWriterLock.ReleaseReaderLock();

  }

  return stringBuilder.ToString();

}

 

SP RequestModule


  当一个新的web应用程序被创建时,SharePoint自动在站点的根目录下增加一个web.config文件,此文件中包含< httpModules >配置单元。由于InitializeAppInstance方法是按照在< httpModules>配置单元中出现的顺序来调用HTTP模块的Init方法的,相关的事件处理程序也按照这个顺序注册的。

 

  SharePoint自带了一个名为SPRequestModule的HTTP模块,注册了大部分的HttpApplication事件句柄,因为这些事件句柄被系统用来初始化SharePoint运行时环境。所以它必须要被放置在所有的HTTP模块前面。SharePoint在其前面加入< clear/ >元素 清除之前注册的所有HTTP模块,以确保SPRequestModule在其它HTTP模块被调用时已经完成运行时环境的初始化。

< httpModules >

<clear / >

<add name=”SPRequest”

type=”Microsoft.SharePoint.ApplicationRuntime.SPRequestModule,

Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral,

PublicKeyToken=71e9bce111e9429c”/>

……

< /httpModules >

 

SPVirtualPathProvider


  SPRequestModule实现IHttpModule接口,实例化SPVirtualPathProvider类,并将其注册到运行时环境中。

  SPVirtualPathProvider virtualPathProvider = new SPVirtualPathProvider();

  HostingEnvironment.RegisterVirtualPathProvider(virtualPathProvider);

 

  ASP.NET 1.1的页面解析器只能装载web前端的文件系统中的页面,在SharePoint 2007以前的版本中,SharePoint自带了一个页面解析器,但是这个解析器无法解析User Control。ASP.NET 2.0的页面解析器把装载的逻辑判断转移到继承自VirtualPathProvider的一个组件中,通过VirtualPathProvider和组件通信完成工作。

 

  SPVirtualPathProvider实现了VirtualPathProvider API,当要装载的页面是Application或者ghosted页面时,文件装载路径将被设置到web前端的文件系统中。当要访问的页面是unghosted页面时,文件装载路径将被设置到内容数据库中。


IHttpHandlerFactory and IHttpHandler


  HttpApplication有一个IExecutionStep类型的MapRequestHandler的事件处理句柄, MapRequestHandle的Execute方法的主要工作是找出处理当前请求的HTTP handler 或者HTTP handler factory,所有的HTTP handler都必须实现IHttpHandler接口,所有的HTTP handler factory都必须实现IHttpHandlerFactory接口。

 

public interface IHttpHandler

{

  void ProcessRequest(HttpContext context);

  bool IsReusable { get; }

}

 

public interface IHttpHandlerFactory

{

  IHttpHandler GetHandler(HttpContext context, string requestType, string url, string pathTranslated);

  void ReleaseHandler(IHttpHandler handler);

}

 

MapRequestHandler的Execute方法通过web.config的< httpHandlers >配置单元来找出对应的事件处理句柄。

< httpHandlers >

< add path=”*.aspx” verb=”*” type=”System.Web.UI.PageHandlerFactory” / >

< add path=”*.ashx” verb=”*” type=System.Web.UI.SimpleHandlerFactory” / >

< add path=”*.asmx” verb=”*”

type=”System.Web.Services.Protocols.WebServiceHandlerFactory,

System.Web.Services, Version=2.0.0.0, Culture=neutral,

PublicKeyToken=b03f5f7f11d50a3a” / >

...

< /httpHandlers >

 

  如上所示,<add>子元素的path属性指定了资源的虚拟路径,verb属性指定一些以逗号分割的HTTP动作,type属性指定了句柄初始化所需要的类型信息。Type属性包括5个以逗号分割的字符串,只有第一个子串是必须指定的,余为可选。

 

SP HttpHandler


  ASP.NET自带了一个HTTP句柄DefaultHttpHandler用来定制所有的请求。当一个web应用程序被创建时,SharePoint自动在web.config的< httpHandlers >配置单元中加入如下代码:

< httpHandlers >

< remove verb=”GET,HEAD,POST” path=”*” / >

< add verb=”GET,HEAD,POST” path=”*”

type=”Microsoft.SharePoint.ApplicationRuntime.SPHttpHandler,

Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral,

PublicKeyToken=71e9bce111e9429c” / >

......

< /httpHandlers >

 

  和所有HTTP句柄一样,DefaultHttpHandler有一个ProcessRequest方法。并实现了IHttpAsyncHandler接口,这个接口有两个方法。

public interface IHttpAsyncHandler : IHttpHandler

{

  IAsyncResult BeginProcessRequest(HttpContext context, AsyncCallback cb, object extraData);

  void EndProcessRequest(IAsyncResult result);

}

  

 

  在DefaultHttpHandler中,实现了异步调用的BeginProcessRequest方法。

 

public virtual IAsyncResult BeginProcessRequest(HttpContext context, AsyncCallback asyncCallback, object asyncState)

{

  this.context = context;

  string virtualPath = OverrideExecuteUrlPath();

  …

  return context.Response.BeginExecuteUrlForEntireResponse(virtualPath,executeUrlHeaders, asyncCallback, asyncState);

  }

  …

}

  如上所示,BeginProcessRequest首先调用OverrideExecuteUrlPath方法,这个方法不执行任何操作,继承自DefaultHttpHandler的子类将完成OverrideExecuteUrlPath方法的具体实现 。BeginExecuteUrlForEntireResponse在其内部调用HttpWorkerRequest 的BeginExecuteUrl方法来执行指定的URL。如前文所述,为了减轻HttpWorkerRequest和IIS之间的通信负荷,BeginExecuteUrl方法实际上直接用新的请求头和URL执行一个新的请求。


  使用派生自DefaultHttpHandler句柄的一个好处是,不管是什么类型的资源,不管是什么样的HTTP动作,其请求都会遍历所有已注册的HTTP模块。所有的HTTP模块都会执行一些预处理任务,并把结果存储到传入的HttpContext对象中。


  因为BeginExecuteUrlForEntireResponse把用户名和认证类型以参数的形式传递给了BeginExecuteUrl方法。所以使用派生自DefaultHttpHandler句柄的另一个好处是,不管是什么类型的资源,不管是什么样的HTTP动作,其请求都会使用同一个ASP.NET认证和授权模块来获取认证和授权。


  SPHttpHandler是DefaultHttpHandler的一个子类,重载了OverrideExecuteUrlPath方法。作为继承的一个重要特性,SPHttpHandler亦会遍历所有的已注册HTTP模块,包括SPRequestModule模块和ASP.NET HTTP 模块。这样,所有请求的SharePoint execution context和ASP.NET execution context都能及时初始化。

 

  因为应用程序域是CLR unloading unit,所以无法将组件从应用程序域中单独地卸载掉,必须将应用程序域卸载同时卸载所有的组件。不包含任何类的组件将被系统删除,如果当前有请求占用该组件,则该组件将被重命名,以待下次应用程序重启时删除。


  当Default.aspx或者Default.aspx.cs的内容有所更正时,ASP.NET页面解析器都将引入一个新的编译的页面,而原来的老的页面组件一直滞留在内存中,直到下一次应用程序重启时被删除,或者当组件滞留到某一上限时,应用程序自动重启。当SharePoint站点非常庞大时,这些滞留在内存中的过期页面组件将产生严重的问题。


  这就是为什么unghosted页面不使用标准的ASP.NET编译过程的原因。unghosted页面被从数据库中直接读出后,传递给ASP.NET页面解析器,SPVirtualPathProvider指定ASP.NET页面解析器以no–compile模式来解析页面,这样页面不被编译成组件,就避免了组件滞留内存的问题,从而大大提高了系统的稳定性。


  由于页面是存储在数据库当中,没有被编译成组件,为了防止数据库注入攻击,缺省在safe mode下面运行,不支持inline code。并且页面只能使用在注册成为安全控件的服务器控件。所以当我们要实现一个自定义服务器控件时,必须在web.config的<SafeControl> 配置单元中增加相关条目。在生成web应用程序时,所有的SharePoint和ASP.NET的组件条目都自动被添加到<SafeControl> 配置单元中。


  当我们用页面模板生成一个站点页面时,这些页面在没有被定制之前均保持在ghosted状态。由于对ghosted页面的访问是通过普通的ASP.NET页面解析器进行的,并没有启动安全模式,所以站点页面将可以包含inline code和服务器控件。此时,如果该页面被定制,其状态将变成unghosted状态,下次访问该页面,SharePoint将自动切换到安全模式,如果这个页面仍然包含那些inline code和服务器控件,将无法正常运行。为了避免这个错误,建议永远不要在页面模板中添加inline code和服务器控件。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值