WCF技术剖析之四:基于IIS的WCF服务寄宿(Hosting)实现揭秘

原文:WCF技术剖析之四:基于IIS的WCF服务寄宿(Hosting)实现揭秘

通过《再谈IIS与ASP.NET管道》的介绍,相信读者已经对IIS和ASP.NET的请求处理管道有了一个大致的了解,在此基础上去理解基于IIS服务寄宿的实现机制就显得相对容易了。概括地说,基于IIS的服务寄宿依赖于两个重要的对象:System.ServiceModel.Activation.HttpModuleSystem. ServiceModel.Activation.HttpHandler

一、通过HttpModule实现服务寄宿

在默认的情况下,基于IIS的服务寄宿是通过一个特殊的HttpModule实现的,其类型为System.ServiceModel.Activation.HttpModule,是一个定义在System.ServiceModel程序集中的内部类型。HttpModule的定义大体上如下面的代码所示,我们很清楚地看到其实现的原理:将实现WCF Service请求处理的逻辑注册到HttpApplication的PostAuthenticationRequest事件中。

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

   1: internal class HttpModule : IHttpModule

   2: {

   3:     //其他成员

   4:     public void Init(HttpApplication context)

   5:     {

   6:         context.PostAuthenticateRequest += new EventHandler(HttpModule.ProcessRequest);

   7:     }

   8:     private static void ProcessRequest(object sender, EventArgs e)

   9:     {

  10:         //服务请求处理实现

  11:     }

  12: }

System.ServiceModel.Activation.HttpModule是一个特殊的HttpModule,说它特别是因为当HttpModule注册到HttpApplication的PostAuthenticateRequest事件处理程序执行后,不会再将请求进一步分发给后续的请求处理步骤。换句话说,就HttpApplication从BeginRequest到EndRequest整个请求处理的生命周期来说,对于基于.svc文件的请求仅仅延续到PostAuthenticateRequest阶段。我们可以通过一种简单的方式来证明这一点。

假设我们有一个WCF服务需要通过IIS进行寄宿,并把WCF服务相应的.svc文件定义在一个对应于某个IIS虚拟目录的ASP.NET Website中。现在我们为之添加一个global.asax,在该global.asax,我通过如下的代码注册了HttpApplication处理请求的前三个事件:BeginRequest、AuthenticateRequest和PostAuthenticateRequest,当这3个事件触发后,将一段代表当前事件的名称写入EventLog中。

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

   1: <%@ Application Language="C#" %>

   2: <%@ Import Namespace= "System.Diagnostics"%>

   3: <script runat="server">

   4:    

   5:     void Application_BeginRequest(object sender, EventArgs e)

   6:     {

   7:         string message = string.Format("BeginRequest Event is raised at {0}", DateTime.Now);

   8:         EventLog.WriteEntry("Application", message, EventLogEntryType.Information);

   9:     }

  10:  

  11:     void Application_AuthenticateRequest(object sender, EventArgs e)

  12:     {   

  13:         string message =  string.Format("AuthenticateRequst Event is raised at {0}",DateTime.Now);

  14:         EventLog.WriteEntry("Application", message, EventLogEntryType.Information);

  15:     }

  16:  

  17:     void Application_PostAuthenticateRequest(object sender, EventArgs e)

  18:     {

  19:         string message = string.Format("PostAuthenticateRequest Event is raised at {0}", DateTime.Now);

  20:         EventLog.WriteEntry("Application", message, EventLogEntryType.Information);

  21:     }

  22: </script>

如果我们上面的说法成立的话,只有HttpApplication的最初3个事件被触发。此外,HttpModule注册的操作会先于定义在global.asax的Application_PostAuthenticateRequest方法执行,那么在整个服务调用过程中,只有Application_BeginRequest和Application_AuthenticateRequest这两个方法会被执行。这一点我们可以从EventLog得到证实。当我们通过执行案例7-2中的代表客户端应用程序后,EventLog中WindowsLog的Application分组中,会多出两个日志项目(之前已经将日志清空),如图1所示。

WCF技术剖析之四:基于IIS的WCF服务寄宿(Hosting)实现揭秘 

图1 通过Event Viewer查看添加的Event Log 

日志的内容正是我们在Application_BeginRequest和Application_AuthenticateRequest方法中定义的日志文本。可见仅仅这两个方法被成功执行,Application_PostAuthenticateRequest方法却没有被执行。可以想象,后续的事件也不可能被触发,如图2所示。

WCF技术剖析之四:基于IIS的WCF服务寄宿(Hosting)实现揭秘 

图2 Event Log的详细内容 

到现在为止,我们仅仅是介绍了如何处理基于.svc文件的请求,并没有说明.svc文件对应的WCF Service是如何被寄宿的。服务的寄宿发生在对服务.svc文件的第一次访问,具体的实现很简单:ServiceMode根据请求的目的地址加载相应的.svc文件,通过解析定义在<%ServiceHost%>指令的Factory和Service属性得到ServiceHostFactory和Service的类型(Factory默认为System.ServiceMode.ServiceHostFactory),通过反射创建继承自基类System.ServiceModel.Activation.ServiceHostFactoryBase的ServiceHostFactory对象。最后通过ServiceHostFactory创建的继承自基类System.ServiceModel.ServiceHostBase的ServieHost对象对Serivce进行寄宿。

二、ASP.NET并行(Side by Side)模式

对于基于IIS服务寄宿,System.ServiceModel.Activation.HttpModule将基于.svc的请求劫持并分发给WCF的服务模型,从而结束了请求在ASP.NET管道的旅程。除了ASP.NET提供的一些少量的底层服务,比如动态编译和AppDomain管理等,绝大部分ASP.NET对传统的ASP.NET资源的请求处理机制将不会应用在基于WCF Service的请求处理流程中。从这个意义上讲,我们可以说WCF Service的运行模式和ASP.NET运行时采用的是一种并行的模式。

你完全可以用一个映射到某个IIS虚拟目录的ASP.NET Website同时作为asmx Web Service和.svc WCF Service的宿主。在这种情况下,ASP.NET .aspx Page、.asmx Web Service和WCF service运行在同一个AppDomain中。但是HttpRuntime对于.aspx Page和.asmx Web Service的处理机制并不会应用于对.svc WCF Service请求。我们把WCF Service这种寄宿模式称为ASP.NET并行(Side by Side)模式,图3揭示了这种寄宿模式。

WCF技术剖析之四:基于IIS的WCF服务寄宿(Hosting)实现揭秘 

图3 ASP.NET并行模式

在图3体现的这种情况下(ASP.NET .aspx Page和.svc WCF Service共存于同一个AppDomain),.aspx可以直接定位WCF Service,它们之间还可以共享一个基于AppDomain的状态,比如类型的静态属性。但是很多ASP.NET特性将不能被WCF Service使用,比如:

  • HttpContext对于WCF Service来说,HttpContext.Current永远为null;
  • 基于文件或者Url的授权:基于.svc文件的ACL(Access Control List)的授权和ASP.NET通过<authorization>定义的基于URL的授权都将失去效力。原因很简单,System.ServiceModel.Activation.HttpModule在PostAuthenticateRequest阶段就将请求劫持,而授权(Authorization)发生在PostAuthenticateRequest之后;
  • HttpModule扩展:作用于PostAuthenticateRequest事件后期的HttpModule将不会生效;
  • 身份模拟(Impersonation):即使通过配置<identity impersonate=”true” />允许身份模拟,WCF Service总是运行在IIS进程账号下。

不过,WCF服务模型通过自己的方式解决了上面的问题,比如:

  • OperationContext:ASP.NET HttpContext是基于当前的请求,WCF的OperationContext是基于当前的操作,本质上是一样的基于上下文的容器;
  • ServiceAuthorizationBehaviorServiceAuthorizationBehavior是一个Service行为,用于实现WCF的授权;
  • DispatchMessageInspector + 自定义Channel:DispatchMessageInspector和自定义Channel分别在服务模型和信道层对入栈消息进行额外的筛选和处理,和自定义HttpModule异曲同工;
  • 基于操作的身份模拟(Impersonation):WCF自身也提供了基于操作的身份模拟实现。

为什么WCF要采用这种于ASP.NET并行的模式,而不像Web Service一样采用与ASP.NET完全兼容呢?这主要是因为WCF和.asmx Web Service有本质的区别:Web Service总是采用IIS寄宿,并使用HTTP作为传输,而WCF则具有不同的寄宿方式,对于传输协议的选择也没有限制。在默认的情况下,不论采用何种寄宿方式,WCF本身的行为应该保持一致。所以,让WCF 服务的行为独立于寄宿的环境与传输协议,是采用并行模式的主要原因。

三、ASP.NET兼容模式

虽然在默认的情况下,IIS的寄宿采用ASP.NET并行的模式。但是在一个Web应用中,尤其是一些AJAX的Web应用,却明确地需要以一种ASP.NET兼容模式处理WCF Service请求。比如,在WCF Service的操作中,需要获取ASP.NET应用的SessionState,或者是需要通过基于.svc文件的ACL对WCF Service进行授权等。

WCF对此提供了支持,实现起来也很简单,对于编程来说,仅仅需要在Service类型加上一个特殊的AspNetCompatibilityRequirementsAttribute特性,并将RequirementsMode属性指定为AspNetCompatibilityRequirementsMode.Allowed,实例代码如下:

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

   1: [AspNetCompatibilityRequirements(

   2: RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]

   3: public class CalculatorService:ICalculator

   4: {  

   5:     //省略成员

   6: }

除此之外,WCF的配置也需要做一些修改,我们需要将<serviceHostingEnvironment/>配置节的aspNetCompatibilityEnabled属性设为true。

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

   1: <?xml version="1.0"?>

   2: <configuration>

   3: <system.serviceModel>    

   4:     <serviceHostingEnvironment aspNetCompatibilityEnabled="true"/>

   5:     <!—其他配置-->

   6:   </system.serviceModel>

   7: </configuration>

在ASP.NET兼容模式下,ASP.NET将会采用与处理.aspx、asmx一样的方式来处理基于.svc的请求,对WCF Service请求的处理将会贯穿HttpApplication请求处理的整个生命周期(从BeginRequest到EndRequest)。对于ASP.NET兼容模式,System.ServiceModel. Activation.HttpModule将忽略对HttpApplication对象PostAuthenticateRequest事件的注册,原本实现在HttpModule中对WCF Service的请求处理逻辑将被一个HttpHandler中:System.ServiceModel.Activation.HttpHandler。如同System.Web.UI.Page(本质上是一个HttpHandler)负责最终处理对.aspx的请求一样,System.ServiceModel.Activation.HttpHandler服务负责最终对.svc的请求。HttpHandler是一个定义在System.ServiceModel程序集中的内部类型。HttpHandler的定义如下,请求处理实现在ProcessRequest方法中,具体的逻辑与实现在System.ServiceModel.Activation.HttpModule中的是完全一致的。

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

   1: internal class HttpHandler : IHttpHandler, IRequiresSessionState

   2: {

   3:     public HttpHandler();

   4:     public void ProcessRequest(HttpContext context);

   5:  

   6:     public bool IsReusable { get; }

   7: }

作者:Artech

出处:http://artech.cnblogs.com

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

上一篇:TIMESTAMP和DATETIME哪个好


下一篇:C++教程|菜鸟教程