通过《再谈IIS与ASP.NET管道》的介绍,相信读者已经对IIS和ASP.NET的请求处理管道有了一个大致的了解,在此基础上去理解基于IIS服务寄宿的实现机制就显得相对容易了。概括地说,基于IIS的服务寄宿依赖于两个重要的对象:System.ServiceModel.Activation.HttpModule和System. ServiceModel.Activation.HttpHandler。
一、通过HttpModule实现服务寄宿
在默认的情况下,基于IIS的服务寄宿是通过一个特殊的HttpModule实现的,其类型为System.ServiceModel.Activation.HttpModule,是一个定义在System.ServiceModel程序集中的内部类型。HttpModule的定义大体上如下面的代码所示,我们很清楚地看到其实现的原理:将实现WCF Service请求处理的逻辑注册到HttpApplication的PostAuthenticationRequest事件中。
<style type="text/css">.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; } </style> <style type="text/css">.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; } </style>1: internal class HttpModule : IHttpModule<!--CRLF-->
2: {
<!--CRLF-->
3: //其他成员<!--CRLF-->
4: public void Init(HttpApplication context)<!--CRLF-->
5: {
<!--CRLF-->
6: context.PostAuthenticateRequest += new EventHandler(HttpModule.ProcessRequest);<!--CRLF-->
7: }
<!--CRLF-->
8: private static void ProcessRequest(object sender, EventArgs e)<!--CRLF-->
9: {
<!--CRLF-->
10: //服务请求处理实现<!--CRLF-->
11: }
<!--CRLF-->
12: }
<!--CRLF-->
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中。
<style type="text/css">.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; } </style>1: <%@ Application Language="C#" %><!--CRLF-->
2: <%@ Import Namespace= "System.Diagnostics"%><!--CRLF-->
3: <script runat="server"><!--CRLF-->
4:
<!--CRLF-->
5: void Application_BeginRequest(object sender, EventArgs e)<!--CRLF-->
6: {
<!--CRLF-->
7: string message = string.Format("BeginRequest Event is raised at {0}", DateTime.Now);<!--CRLF-->
8: EventLog.WriteEntry("Application", message, EventLogEntryType.Information);<!--CRLF-->
9: }
<!--CRLF-->
10:
<!--CRLF-->
11: void Application_AuthenticateRequest(object sender, EventArgs e)<!--CRLF-->
12: {
<!--CRLF-->
13: string message = string.Format("AuthenticateRequst Event is raised at {0}",DateTime.Now);<!--CRLF-->
14: EventLog.WriteEntry("Application", message, EventLogEntryType.Information);<!--CRLF-->
15: }
<!--CRLF-->
16:
<!--CRLF-->
17: void Application_PostAuthenticateRequest(object sender, EventArgs e)<!--CRLF-->
18: {
<!--CRLF-->
19: string message = string.Format("PostAuthenticateRequest Event is raised at {0}", DateTime.Now);<!--CRLF-->
20: EventLog.WriteEntry("Application", message, EventLogEntryType.Information);<!--CRLF-->
21: }
<!--CRLF-->
22: </script>
<!--CRLF-->
如果我们上面的说法成立的话,只有HttpApplication的最初3个事件被触发。此外,HttpModule注册的操作会先于定义在global.asax的Application_PostAuthenticateRequest方法执行,那么在整个服务调用过程中,只有Application_BeginRequest和Application_AuthenticateRequest这两个方法会被执行。这一点我们可以从EventLog得到证实。当我们通过执行案例7-2中的代表客户端应用程序后,EventLog中WindowsLog的Application分组中,会多出两个日志项目(之前已经将日志清空),如图1所示。
图1 通过Event Viewer查看添加的Event Log
日志的内容正是我们在Application_BeginRequest和Application_AuthenticateRequest方法中定义的日志文本。可见仅仅这两个方法被成功执行,Application_PostAuthenticateRequest方法却没有被执行。可以想象,后续的事件也不可能被触发,如图2所示。
图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揭示了这种寄宿模式。
图3 ASP.NET并行模式
在图3体现的这种情况下(ASP.NET .aspx Page和.svc WCF Service共存于同一个AppDomain),.aspx可以直接定位WCF Service,它们之间还可以共享一个基于AppDomain的状态,比如类型的静态属性。但是很多ASP.NET特性将不能被WCF Service使用,比如:
不过,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,实例代码如下:
<style type="text/css">.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; } </style> 1: [AspNetCompatibilityRequirements(
<!--CRLF-->
2: RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
<!--CRLF-->
3: public class CalculatorService:ICalculator<!--CRLF-->
4: {
<!--CRLF-->
5: //省略成员<!--CRLF-->
6: }
<!--CRLF-->
除此之外,WCF的配置也需要做一些修改,我们需要将<serviceHostingEnvironment/>配置节的aspNetCompatibilityEnabled属性设为true。
<style type="text/css">.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; } </style>1: <?xml version="1.0"?><!--CRLF-->
2: <configuration><!--CRLF-->
3: <system.serviceModel><!--CRLF-->
4: <serviceHostingEnvironment aspNetCompatibilityEnabled="true"/><!--CRLF-->
5: <!—其他配置--><!--CRLF-->
6: </system.serviceModel><!--CRLF-->
7: </configuration><!--CRLF-->
在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中的是完全一致的。
<style type="text/css">.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; } </style>1: internal class HttpHandler : IHttpHandler, IRequiresSessionState<!--CRLF-->
2: {
<!--CRLF-->
3: public HttpHandler();<!--CRLF-->
4: public void ProcessRequest(HttpContext context);<!--CRLF-->
5:
<!--CRLF-->
6: public bool IsReusable { get; }<!--CRLF-->
7: }
<!--CRLF-->
注:部分内容节选自《WCF技术剖析(卷1)》第七章:客服务寄宿(Service Hosting)