在WCF之前,ASMX是ASP.NET Web 服务中一个公共处理方式。它对公共Web服务需求提供了出色的支持并通过ASP.NET HTTP管道提供了鲁棒性扩展能力。在WCF中,服务被设计为不需了解它们的寄宿模且独立传输。所以WCF服务不能依赖于HTTP管道内部的实现,比如HTTP.SYS。
和ASMX一样,WCF也提供一个鲁棒性扩展模型。但是除了使用HTTP管道,它也采用信道栈。WCF中的信道非常灵活。它们了解传输协议,比如HTTP,但是也了解其他的协议元素比如安全和事务。信道栈在第三章”信道”和第四章”绑定”中描述。
WCF支持IIS中的一个特殊寄宿模型: ASP.NET 兼容模式。当运行在这个模式中时,ASP.NET 为WCF服务提供寄宿环境。因此,在<system.web/hostingEnvironment>和<system.web/compilation>是合法的。然而,并不是所有的ASP.NET HTTP特性都在这个模式中启用:
1. HTTPContext.Current. 在ASP.NET HTTP 管道中设置为空。在一个WCF服务中,你可以使用OperationContext.Current对象来实现类似的目的。
2. 文件/Url 认证
3. 模仿
4. 会话状态
5. <system.web/Globalization>
6. ConfigurationManager.AppSettings. 你仅可以在web.config 的根节点或者虚拟应用上面获得设置,因为httpContext是空的。
为了开启运行在ASP.NET兼容模式的ASP.NET 特性,需要改两个设置。在应用层,你必须在web.config中设置<system.serviceModel>/<serviceHostingEnvironment>/<aspNetCompatibilityEnabled>为true。因为ASMX在服务层是一个选择进入模式,你不许在服务层设置AspNetCompatibilityRequirements为Allowed。通过这两个设置,几乎所有的ASP.NET特性都可以在WCF服务中使用。表7.1描述了这两种设置的关系。
表7.1 在一个WCF服务中允许ASMX特性的设置
然而,有一些部分需要更进一步的解释。
1. HTTPContext.Current。ConfigurationManager.AppSettings和ConfigurationManager.GetSection同时工作。HttpContext.Current将在WCF线程间流转。
2. 全球化。你可以设置线程的文化区域并在<system.web>中访问国际化部分。
3. 模仿。WCF支持使用行为在服务层和操作层实现。这是ASP.NET的额外实现方式。如果服务通过WCF模仿,会在ASP.NET中重载设置。如果服务没有实现模仿,ASP.NET 规则将会使用。
4. 会话状态。这是完全继承自ASP.NET配置的实现。你可以使用进程,服务或者SQL 持续结构来保存状态。
在ASP.NET 兼容模式开启后,服务可以利用ASP.NET的特性。在列表7.5中,我们使用两个ASP.NET特性。首先,我们使用ASMX的SessionState特性来存储会话层状态。实例可以设置为PerCall, PerSession或者Single.这些在第五章”行为”中被深入定义。在这个例子中,我们使用PerSession,以便于如果一个客户端多次使用同样的代理调用服务,会话状态将会在不同调用间保存。WCF中有很多其他的方式来存储会话层数据,但是对那些对ASMX很熟悉的人来说,这是一个方便的架构。其次,我们在web.config中使用熟悉的AppSettings部分来存储特殊应用的配置数据。在服务端代码,ConfigurationManager对象的AppSettings集合用来收集这些值。
列表7.5 访问ASMX会话状态和配置设置
using System.ServiceModel; using System.Runtime.Serialization; using System.ServiceModel.Activation; using System.Web; using System.Configuration; namespace Services { [DataContract] public class StockPrice { //[DataMember] public string source; [DataMember] public double price; [DataMember] public int calls; } [ServiceContract] public interface IStockService { [OperationContract] StockPrice GetPrice(string ticker); } [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)] [AspNetCompatibilityRequirements(RequirementsMode= AspNetCompatibilityRequirementsMode.Required)] public class StockService : IStockService { public StockPrice GetPrice(string ticker) { StockPrice p = new StockPrice(); int nCalls = 0; if (HttpContext.Current.Session["cnt"] != null) { nCalls = (int)HttpContext.Current.Session["cnt"]; } HttpContext.Current.Session["cnt"] = ++nCalls; p.calls = nCalls; p.price = 94.85; //p.source = ConfigurationManager.AppSettings["StockSource"]; return p; } } }
为了让PerSession实例工作,在客户端必须保存一个会话身份标识以便于从客户端到服务端的顺序调用可以将会话ID返回给服务端。对于ASP.NET,这是通过在HTTP头中传输的一个客户端cookie实现的。为了让通过ASMX的PerSession实例工作,客户端必须开启cookies.因为标准HTTP绑定,basicHttpBinding和wsHttpBinding,默认情况下不允许cookies,你必须在客户端app.config中定义一个AllowCookies=true的绑定配置。列表7.6显示了在服务端使能aspNetCompatibility.
列表7.6 在服务配置使能ASP.NET 兼容
<?xml version="1.0" encoding="utf-8" ?> <configuration> <system.serviceModel> <serviceHostingEnvironment aspNetCompatibilityEnabled="true" /> <behaviors> <serviceBehaviors> <behavior name="MEXServiceTypeBehavior"> <serviceMetadata httpGetEnabled="true" /> </behavior> </serviceBehaviors> </behaviors> <bindings /> <services> <service behaviorConfiguration="MEXServiceTypeBehavior" name="Services.StockService"> <endpoint address="" binding="basicHttpBinding" bindingConfiguration="" contract="Services.IStockService" /> <endpoint address="mex" binding="mexHttpBinding" bindingConfiguration="" contract="IMetadataExchange" /> </service> </services> </system.serviceModel> </configuration>
列表7.7 显示了如何在客户端配置文件使能cookies.这个列表由Visual Studio的添加服务引用生成。注意默认的allowCookies设置为true.
列表7.7 在客户端配置文件中使能Cookies
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="BasicHttpBinding_IStockService" closeTimeout="00:01:00"
openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00"
allowCookies="true" bypassProxyOnLocal="false" hostNameComparisonMode="StrongWildcard"
maxBufferSize="65536" maxBufferPoolSize="524288" maxReceivedMessageSize="65536"
messageEncoding="Text" textEncoding="utf-8" transferMode="Buffered"
useDefaultWebProxy="true">
<readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
maxBytesPerRead="4096" maxNameTableCharCount="16384" />
<security mode="None">
<transport clientCredentialType="None" proxyCredentialType="None"
realm="">
<extendedProtectionPolicy policyEnforcement="Never" />
</transport>
<message clientCredentialType="UserName" algorithmSuite="Default" />
</security>
</binding>
</basicHttpBinding>
</bindings>
<client>
<endpoint address="http://localhost/StockService.svc"
binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_IStockService"
contract="localhost.IStockService" name="BasicHttpBinding_IStockService" />
</client>
</system.serviceModel>
</configuration>
WCF中的开启ASP.NET 模拟与.NET 1.X 中的实现方式一样。通过在web.config文件的<system.web>部分包含<identity impersionate=”true”>实现的。当你做这个时,客户端验证被自动发送给服务端,服务端使用客户端验证来执行操作。
使能模仿可以使用两种方式中的任意一种。为了在服务层设置它,在服务行为中使用impersionateCallerForAllOperations=true,在操作行为中使用ImpersionationOption.Allowed.为了在操作层开启它,在操作行为中使用ImpersonationOption.Required,同时不在服务行为中引用任何内容。
列表7.8显示了操作层的模拟设置,假设它没有在web.config的服务层允许。当一个客户端访问服务端时,用户的登录身份在RequestedBy成员中返回。如果操作行为被移除,RequestedBy成员默认风拿回网络服务。模拟会在第八章”安全”详细介绍。
列表7.8 使能模拟
using System.ServiceModel;
using System.Runtime.Serialization;
using System.ServiceModel.Activation;
using System.Web;
using System.Configuration;
using System.Security.Principal;
namespace Services
{
[DataContract]
public class StockPrice
{
[DataMember] public double price;
[DataMember] public string requestedBy;
}
[ServiceContract]
public interface IStockService
{
[OperationContract]
StockPrice GetPrice(string ticker);
}
[ServiceBehavior]
[AspNetCompatibilityRequirements(RequirementsMode= AspNetCompatibilityRequirementsMode.Required)]
public class StockService : IStockService
{
[OperationBehavior(Impersonation=ImpersonationOption.Required)]
public StockPrice GetPrice(string ticker)
{
StockPrice p = new StockPrice();
p.requestedBy = WindowsIdentity.GetCurrent().Name;
p.price = 94.85;
return p;
}
}
}