`
lzy.je
  • 浏览: 148496 次
  • 性别: Icon_minigender_1
  • 来自: 沈阳
社区版块
存档分类
最新评论

Flex 3 RIA 应用 + WCF 服务开发注意事项(2009-5-20 updated)

阅读更多

           这两天应需要研究了下 WCF 服务和 Flex 3 RIA 相关的开发技术,并上手写了一些代码,发现之前的考虑有不少细节问题需要注意、解决,打算用这篇持续更新的文章来做以记录,将通过 Flex 3 RIA 应用来使用 WCF 服务过程中发现的问题和解决方法以注意事项的形式记录清楚。正所谓吃一堑长一智,也请大家多多分享,一起补充。

 

  1. WCF 服务使用的 SOAP 版本
  2. “Provider com.bea.xml.stream.MXParserFactory not found” 异常
  3. 启用 “basicHttpBinding” 绑定的 Session 支持
  4. “The maximum array length quota (16384) has been exceeded while reading XML data” 异常
  5. 配置垮域访问安全策略文件 crossdomain.xml
  6. “The communication object, System.ServiceModel.Channels.ServiceChannel, cannot be used for communication because it is in the Faulted state.”异常

 

WCF 服务使用的 SOAP 版本

          当前发布的 Flex 3.2.0 build 3958 版本仅支持与使用了 SOAP 1.1 协议的 Web 服务通信,因此如果想在 Flex 3 RIA 应用中使用 WCF 服务,则必须配置 WCF 服务为底层使用 SOAP 1.1 协议通信。具体方法可以通过更改服务配置文件 Web.config 来完成。

 

    <services>
      <service behaviorConfiguration="CEELS.Service.BaseBehavior" name="CEELS.Service.CEELSSvc">
        <endpoint address="" binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_ICEELSSvc" contract="CEELS.Service.ICEELSSvc">
          <identity>
            <dns value="localhost" />
          </identity>
        </endpoint>
        <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
      </service>
    </services>

    <bindings>
      <basicHttpBinding>
        <binding name="BasicHttpBinding_ICEELSSvc" allowCookies="true" />
      </basicHttpBinding>
    </bindings>
 

          通过上面的配置可见,这里的 CEELSSvc WCF 服务使用了 basicHttpBinding 内置 binding,它默认使用 SOAP 1.1 作为底层通信协议。之前也尝试过使用下面的配置(片段),完全自定义 binding,目的是想既能够使用 SOAP 1.1 又能够启用 Session 支持,但经过测试发生该方法并不可行,请大家别绕弯路原因可参见下节描述。 另外关于为在 Flex 3 RIA 应用(包括 Silverlight)中使用的 WCF 服务启用 Session 支持,请参见下面的 “启用 ‘basicHttpBinding’ 绑定的 Session 支持” 一节。

 

    <services>
      <service behaviorConfiguration="CEELS.Service.BaseBehavior" name="CEELS.Service.CEELSSvc">
        <endpoint address="" binding="customBinding" bindingConfiguration="Soap11AddressingBinding" contract="CEELS.Service.ICEELSSvc">
          <identity>
            <dns value="localhost" />
          </identity>
        </endpoint>
        <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
      </service>
    </services>


    <bindings>
      <customBinding>
        <binding name="Soap11AddressingBinding">
          <textMessageEncoding messageVersion="Soap11WSAddressing10" />
          <transactionFlow transactionProtocol= "WSAtomicTransactionOctober2004" />
          <httpTransport />
          <reliableSession />
        </binding>
      </customBinding>
    </bindings>

 

“Provider com.bea.xml.stream.MXParserFactory not found” 异常

          当准备好 WCF 服务后,就可以在 Flex Builder 中通过 WSDL 来导入服务了,所谓的导入过程其实就是为 Flex 3 RIA 应用生成 ActionScript 3 描述的服务本地代理对象集的过程。我在首次导入时,Flex Builder 就报出了 “Provider com.bea.xml.stream.MXParserFactory not found” 异常,可以 google 到这个异常应该与 StAX 有关。在 Adobe 的 jira 系统中也可以看到也有关这个情况的两个报告:

 

  1. http://bugs.adobe.com/jira/browse/FB-13542
  2. http://bugs.adobe.com/jira/browse/FB-13000

          可以发现造成这个问题的原因是由于在服务 WSDL 中包含了 “policy” 标签,一旦有该标签,则在导入服务的过程中就会报出这个异常。个人觉得这属于 bug 范围,毕竟策略标签也属于正常 WSDL 的一部分。不过通过 jira 可以看到 Adobe 并没有确认该 bug。

 

<sp:SymmetricBinding xmlns:sp="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy">
  <wsp:Policy>
    <sp:ProtectionToken>
      ...
    </sp:ProtectionToken>
    <sp:AlgorithmSuite>
      <wsp:Policy>
        <sp:Basic256 />
      </wsp:Policy>
    </sp:AlgorithmSuite>
    <sp:Layout>
      <wsp:Policy>
        <sp:Strict />
      </wsp:Policy>
    </sp:Layout>
    <sp:IncludeTimestamp />
    <sp:EncryptSignature />
    <sp:OnlySignEntireHeadersAndBody />
  </wsp:Policy>
</sp:SymmetricBinding>

 

          这里的解决方法也比较简单,实际上和上面第一节所说的方法相同,即只要 WCF 服务使用了 basicHttpBinding 绑定就可以避免该问题,因为这样服务生成的 WSDL 不包括策略相关标签。 这也上面一节后面给出的配置不可行的原因。

 

启用 “basicHttpBinding” 绑定的 Session 支持

          实际上不单是 Flex 3 RIA 应用,最新版本的 Silverligth 2 应用都只能够使用 basicHttpBinding 绑定的 WCF 服务,而在使用 basicHttpBinding 绑定时 WCF 框架会采用 per-call 的服务对象激活方式,即在每个服务方法被客户端调用时,都会激活一个新的服务对象来执行方法代码。与之相对应的还有 singleton 和 per-session 两种服务对象激活方式,即单一服务对象实例和第个客户端代理对象会话对应一个服务对象实例两种激活方式。实际上 per-call 与 singleton 与 .NET Remoting 时代的 well know 对象二种激活方式对应,而 per-session 与客户端激活方式对应。话说回来,既然 basicHttpBinding 绑定不是 per-session 的,必然服务对象的状态(成员变量值)是保存不住的,因为每次请求都会重新创建服务对象实例。解决方式也是有的,就是通过启用 WCF 对 ASP.NET 兼容方式。不过相对 WCF 的设计初衷就有些退化了,这还需 Flex 3 / Silverligth 团队多多努力了,呵呵。方法如下。

 

1. 配置 WCF Host 的 ASP.NET 兼容模式

 

首先需要在服务接口(契约)的实现类上添加 AspNetCompatibilityRequirements 属性:

 

[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]

 

 

[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Required)]

 

接下来在 Web.config 的 <system.serviceModel> 节中添加如下配置:

 

<serviceHostingEnvironment aspNetCompatibilityEnabled = "true"/>

 

2. 启用 Session 处理

 

向 Web.config 的 <system.web> 节中添加如下配置,分别用于 cookies 和 cookieless 两种情况。

 

<sessionState mode = "InProc" cookieless = "false" regenerateExpiredSessionId = "true" timeout = "20"/>

 

 

<sessionState mode = "InProc" cookieless = "true" regenerateExpiredSessionId = "false" timeout = "20"/>

 

需要说明的是,当前 Silverlight 2 (默认生成的本地 WCF 服务代理)不支持 cookieless 形式的 cookies。

 

3. 启用 Cookies 支持

 

          需要从 WCF 服务端和客户端 (Flex 3 RIA 还待研究,那位 guru 来分享下:P)两端来配置启用 Cookies 支持。通过 Web.config 和 App.config 配置文件着手。首先来更新 WCF 服务端,在 Web.config 中添加如下绑定配置内容。

 

    <bindings>
      <basicHttpBinding>
        <binding name="BasicHttpBinding_ICEELSSvc" allowCookies="true" />
      </basicHttpBinding>
    </bindings>

 

          接下来还需要更新测试 / Silverlight 客户端,在 App.config 中添加/编辑和上边相同的绑定配置内容,和面的配置类似,这里就再不罗列了。

 

          通过上边三点配置、修改,就可以在 WCF 服务端使用兼容 ASP.NET 方式的 HttpContext.Current.Session 对象了。当在 WinForm 类型的应用中使用该 WCF 服务时,其行为如同 per-session,即每个客户端创建的本地服务代理对象都对应有不同的 Session 对象(Session ID 不同);而在 ASP.NET 或 Silverligth 应用中使用该服务时,其行为与在 WinForm 类型的应用中不同,即由于使用了与 ASP.NET 兼容的 Session 处理方式,所以同一 ASP.NET / Silverlight 会话中创建的所有本地服务代理对象都只对应一个服务端 HttpContext.Current.Session 对象,因为它们发给 WCF 服务端的 Session ID 都是相同的。

 

在 Silverlight 论坛中也有对这个问题的讨论。

Silverlight-basicHttpBinding-WCF: How to handle Session

 

// 2009.03.25 添加 ////

 

          通过在 Flex 3 程序中成功调用包括 Session 的 WCF 服务,已经验证了该方法在 Flex 3 RIA + WCF 服务使用场景中是可行的。Flex 3 RIA 的客户端不用再进行任何其它配置,只要将 WCF 暴露出的 WSDL 作为服务向导的源导入项目中即可,Flex Builder 向导会生成客户端代码,不过需要注意的是 WCF 中最好不要包括 AbstractWebService 等类中已有的方法,后者是 Flex 3 框架自带的类,其做为所有 WebServer RIA 本地服务代理对象的基类,Flex Builder 向导不能识别出服务方法与 AbstractWebService 等类中已有的方法的重名情况(我这是就撞到了 logout 方法),等编译 RIA 时就会出错了。

 

          另外,还需要说明的,和 Silverlight 一样,Flex RIA 应用只支持服务方法的异步调用 ,根本原因在于如果浏览器的 UI 线程被阻塞的话(同步调用),那么无论是 Flash player 还是 Silverlight runtime 将都不能得到异步响应的结果。这里可参见 Silverlight 项目给出的相关说明,道理和 Flex 是一样的。

 

The primary reason is that the browser plugin networking APIs only support
async calls on the UI thread. This means we are blocked from supporting
sync requests on the UI thread. (If we blocked on the UI thread, we would never get the response callback from the browser.) Looking at an API friendliness, scenarios, and resourcing priority, we don’t support a “simulated” sync call on a background thread.

 

异步调用相当简单,举个例子。 

 

private function onLoginButtonClick() : void
{
	this.ceelssvc.addauthenticateEventListener(onAuthenticateReturn);
	
	this.ceelssvc.authenticate(this.userNoTextInput.text, this.passwordTextInput.text, userType);
}
	
private function onAuthenticateReturn(event : AuthenticateResultEvent) : void
{
	this.ceelssvc.removeEventListener(AuthenticateResultEvent.Authenticate_RESULT, onAuthenticateReturn);

	if (!event.result)
		// Successed.
	else
		// Failed.
}

 

// End ////

 

// 2009.05.20 22:12 添加 ////

 

          在异步编程模型下,执行一个“任务”的代码被分割为“多段”式调用,典型的是成对的异步方法调用:一个方法用于发起异步请求,另一个方法用于结束异步请求并完成剩余的“任务”。与传统顺序方法调用方式相比,异步调用时的上下文数据不能存放在线程栈上,因此不如顺序结构共享数据变量方便;另外传统顺序方法调用中很容易使用的 try…catch…finally 结构化异常处理和 using 等关键字来保证资源正确、安全性,都会由于无法跨越方法边界而使代码在处理异常和资源保护等方面需要花更大的精力。基于上述现状,很需要一种方法来简化异步编程中的这此问题,在我这里涉及的这个 Flex 应用中也出现了很多的这类问题,随着业务逻辑复杂性的增长和代码的增加,完成一个“业务”会涉及到很多方法调用(这些方法的分立更多是从功能代码复用来考虑),它们被异步模型限定在互相嵌套的结构中。此时就会出现上述的问题,对于上下文数据的共享和部分失败(partial failure)的异常处理也极其困难。

 

          之前这个问题都很困扰我,前两天找到了2篇文章,介绍了通过 Microsoft CCR 和 Wintellect's AsyncEnumerator 组件解决此类问题的原理和方法,很值得我去学习、掌握。本质上就简化异步编程模型来说,它们都是通过将“任务”所涉及的多个分步操作,通过生成多个 IEnumerator 指向的成员,在外部基础结构中等待、控制这些成员的异步执行来简化“任务”相关代码结构的,用起来很简单、实用。下篇文章更是精彩,对第一篇提了出的性能上的设计缺陷进行了弥补。

 

简化异步操作(上):使用 CCR 和 AsyncEnumerator 简化异步操作
简化异步操作(下):构建 AsyncTaskDispatcher 简化多个异步操作之间的协作调用

 

          另外需要特别说的是 CCR,它是 Microsoft 机器人小组的一个代码库,引述如下。这两天有时间研究了下,正在深入中。

 

CCR 用户指南 写道
并发与协调运行时 (Concurrency and Coordination Runtime) 是一个可以从任意 .net 2.0 程序语言进行访问的托管代码库。CCR 解决了面向服务应用对管理异步操作、处理并发、利用并行硬件 和处理部分失败 (partial failure) 的需求。它使得你设计的应用的软件模块和/或组件之间可以松耦合;这样就可以让它们可以被独立的开发,而且对运行环境和其它组件的依赖降低到最小。这种方法会改变你对程序的认识,从开始设计流程到使用同一的方式处理并发、失败和隔离。

 

使用并发与协调运行时

 

// End ////

 

“The maximum array length quota (16384) has been exceeded while reading XML data” 异常

          如果开发的 WCF 服务中包括了上传、下载文件或比较大的数据量时,就有可能引发该异常。在我的服务中有上传和下载文件的功能,当将一个上传文件数据保存在 byte[] 后提交给服务时,在 WCF 反序列化时抛出该异常,详细信息如下。

 

The formatter threw an exception while trying to deserialize the message: Error in deserializing body of request message for operation ‘XXX’. The maximum array length quota (16384) has been exceeded while reading XML data. This quota may be increased by changing the MaxArrayLength property on the XmlDictionaryReaderQuotas object used when creating the XML reader. Line 1, position 39987.

 

          解决方法也很简单,正如异常中所讲的,由于默认的 maximum array length quota 长度为 16384,而提交的 byte[] 数据长度超出了该限定范围,因此我们仅需要将 maximum array length quota 配置调大即可。这需要在两个地方分别做调整,一个是 WCF 服务端的 Web.config,另一个是客户端 App.config 文件。呵呵,在用 Flex 3 的兄弟先别着急,当前我在着手开发 WCF 服务端,Flex 3 RIA 客户端还未开始,为了验证 WCF 服务功能,我是通过 Visual Studio 2008 建立单元测试项目来完成测试的,因此才有了上面说的需要调整的客户端 App.config 文件这回事。关于这个问题在 Flex 3 RIA 应用中需要做的具体配置,我会在使用后添加该内容。

首先来调整 WCF 服务端的 Web.config 文件。

 

    <bindings>
      <basicHttpBinding>
        <binding name="BasicHttpBinding_ICEELSSvc" allowCookies="true">
          <readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="163840000" maxBytesPerRead="4096" maxNameTableCharCount="16384" />
        </binding>
      </basicHttpBinding>
    </bindings>

 

          其中 maxArrayLength 值就是配置 maximum array 长度限制的,在这里我配置成了 163840000 大,呵呵够用了。其它参数的意思可参见 MSDN 文档。需要注意的是,该 BasicHttpBinding_ICEELSSvc binding 需要在服务的 endpoint 中用 bindingConfiguration 指定。同样,我们需要在客户端的 App.config 中做相应的配置。我这里配置完的 App.config 文件内容如下。

 

                <binding name="BasicHttpBinding_ICEELSSvc" 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="163840000"
                        maxBytesPerRead="4096" maxNameTableCharCount="16384" />
                    <security mode="None">
                        <transport clientCredentialType="None" proxyCredentialType="None"
                            realm="" />
                        <message clientCredentialType="UserName" algorithmSuite="Default" />
                    </security>
                </binding>

 

其中的 maxArrayLength 值与服务端的配置相对应即可。

 

// 2009.04.12 添加 ////

 

          通过实践,发现上面对该异常的说明还有遗漏,通过设置 maxArrayLength 大小只是用上 WCF 上传文件的必要基础,实际上想通过 WCF 上传下载文件,以至到大文件(百M到G),还需要对 WCF 流模式的 binding 有个很好的理解和利用。而且,如果使用 asp.net、silverlight 开发上传下载文件的客户端的话,还需要配置 httpRuntime 选项。

 

WCF 服务有四种 transferMode:

  1. Buffered
  2. Streamed
  3. StreamedRequest
  4. StreamedResponse

          使用 Streamed 或 Buffered(chunked)二种方式都可以实现上传下载文件,但是很明显基于 Buffered 的方式上传大文件很困难,因为很难想象将大文件都装入内存并一次性放入 SOAP 报文中,这也就需要我们创建一个固定大小的 buffer 如 4 KB大小 ,然后将文件一块一块的发送给 WCF 服务端处理,这要经过好多次的 SOAP 推送。因此,通过 Stream 来上传下载文件是最好的方案,但是这样也会有四个限制。

  1. 只有 BasicHttpBinding、NetTcpBinding 和 NetNamedPipeBinding 支持传送流数据;
  2. 流数据类型必须是可序列化的 Stream 或 MemoryStream;
  3. 传递时消息体(Message Body)中不能包含其他数据;
  4. TransferMode 的限制和 MaxReceivedMessageSize 的限制等。

          了解了上面这些,写出上传和下载文件的 WCF 服务端和客户端代码就很容易了,这里有个例子,可供参考《使用WCF上传文件 》,如果想了解得稍微多此的话,可以再看看这个《WCF Streaming: Upload files over HTTP 》。

 

          上面给出的这二个例子都说明了如果上传文件,我这里还要说说下载文件时需要注意的一个细节。一般来说,我们在服务端会如果处理上传的文件数据呢?一种方式是存入数据库,另一种是将其存入文件系统中(特定磁盘、存储),一般来讲我个人更偏重于将其写入文件系统而不应考虑写入数据库中,原因主要是数据库性能和易维护性。可以想到,基于流的方式来处理文件的数据下载时,如果我们文件内容存在数据库中时,我们将使用 MemoryStream (大文件要消耗多很多内存的);而如果存在文件系统中时,我们将使用 FileStream。这里要说的就是使用 FileStream 时的问题。我们在开发下载服务时,很容易写出下面的代码。

 

var filePath =
	Path.Combine("c:/uploadfiles", fileId.ToString());

if (File.Exists(filePath ))
	return new FileStream(filePath, FileMode.Open);

 

          这里的问题就出在 new FileStream() 上,一方面为了返回流我们打开文件,另一方面我们没有去关闭它。那么结果会比较严重,这个文件一旦被下载过(即被 FileStream 打开过),那么只要 IIS (w3p 进程)不关闭,这个文件就永远不能进行修改、删除等操作,因为文件正在被使用。这也就意味着,那么维护上传文件的 WCF 服务方法都会在此类文件上操作异常。而且这个 FileStream 尽管在调用服务的客户端被关闭也是不会有什么改善。解决方法是,不要直接在下载方法中直接返回流,而是通过设计一个包括下载文件流的返回 MessageContract,并使其继承 IDisposable 接口,并在 IDisposable.Dispose 方法中关闭该下载文件流对象。因为 WCF 服务框架会处理实现 IDisposable 接口的 MessageContract,并正确调用它的 Dispose 方法。下面给出了示例代码,供参考。

 

public interface IResourceLoader
{
	[OperationContract]
	void UploadResource(FileUploadRequestMessage fileUploadMessage);

	[OperationContract]
	FileDownloadResponseMessage DownloadResourceById(FileDownloadRequestMessage fileDownloadRequestMessage);

	[OperationContract]
	void DeleteResource(string userNo, string password, UserType userType, int id);
}

[MessageContract]
public class FileUploadRequestMessage : IDisposable
{
	[MessageHeader(MustUnderstand = true)]
	public string UserNo;

	[MessageHeader(MustUnderstand = true)]
	public string Password;

	[MessageHeader(MustUnderstand = true)]
	public UserType UserType;

	[MessageHeader(MustUnderstand = true)]
	public int ResourceTagId;

	[MessageHeader(MustUnderstand = true)]
	public string ResourceName;

	[MessageHeader(MustUnderstand = true)]
	public string FileName;

	[MessageBodyMember(Order = 1)]
	public Stream FileContent;

	[MessageHeader(MustUnderstand = true)]
	public string ContentType;

	[MessageHeader(MustUnderstand = true)]
	public string Remark;

	#region IDisposable Members

	public void Dispose()
	{
		if (this.FileContent != null)
			this.FileContent.Close();
	}

	#endregion
}

[MessageContract]
public class FileDownloadRequestMessage
{
	[MessageHeader(MustUnderstand = true)]
	public string UserNo;

	[MessageHeader(MustUnderstand = true)]
	public string Password;

	[MessageHeader(MustUnderstand = true)]
	public UserType UserType;

	[MessageHeader(MustUnderstand = true)]
	public int ResourceId;
}

[MessageContract]
public class FileDownloadResponseMessage : IDisposable
{
	[MessageHeader(MustUnderstand = true)]
	public string FileName;

	[MessageBodyMember(Order = 1)]
	public Stream FileContent;

	[MessageHeader(MustUnderstand = true)]
	public string ContentType;

	#region IDisposable Members

	public void Dispose()
	{
		if (this.FileContent != null)
			this.FileContent.Close();
	}

	#endregion
}

 

public partial class CEELSInfrastructure : IResourceLoader
{
	#region IResourceLoader Members

	public virtual FileDownloadResponseMessage DownloadResourceById(FileDownloadRequestMessage fileDownloadRequestMessage)
	{
		... ...

		var fileDownloadMessage = new FileDownloadResponseMessage();

			fileDownloadMessage.FileName = "filename";
			fileDownloadMessage.ContentType = "text/pline";
		var filePath =
			Path.Combine("c:/uploadfiles", fileId.ToString());

		if (File.Exists(filePath ))
			fileDownloadMessage.FileContent = new FileStream(filePath, FileMode.Open);

		return fileDownloadMessage;
	}

	... ...

	#endregion
}

 

          通过上面的方法就可以在下载文件完成后(Stream 传输完成后),由 WCF 服务框架关闭 FileStream 对象,释放文件句柄(native handler)。

 

// End ////

 

配置垮域访问安全策略文件 crossdomain.xml

          要通过 Flex RIA 应用访问服务,还需要在 WCF 服务域的站点根目录中存放一个名为 crossdomain.xml 的垮域访问安全配置文件。它的内容很简单,包括服务所允许的 IP 地址、端口信息,以及 HTTP 请求的头内容判定等。关于这个的详细配置可以参考 Adobe 给出的文档 Cross-domain policy file specification 。需要注意的是,从 Flash 9 Player 开始,Adobe 修订了该文件结构及其安全控制机制,因此迁移老应用的兄弟要注意了。下面是我为 WCF 服务端配置的 crossdomain.xml,在这里是用在调试环境,因此也就全都放开了。将其存放在服务站点的根目录中,对应 IIS 就是站点主目录,而对应 ASP.NET Developement Server 的开发环境来说默认将是项目的目录。

 

<?xml version="1.0"?>
<!DOCTYPE cross-domain-policy SYSTEM "http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd">
<cross-domain-policy>
  <site-control permitted-cross-domain-policies="all" />
  <allow-access-from domain="*" />
  <allow-http-request-headers-from domain="*" headers="*" />
</cross-domain-policy>

 

// 2009.04.12 15:39 添加 ////

 

“The communication object, System.ServiceModel.Channels.ServiceChannel, cannot be used for communication because it is in the Faulted state.”异常

          我们在设计 WCF 服务时,很自然的会用到异常处理,如调用服务时传入不正确的参数、超出预定范围的数据等等。此时,我们会在检查、捕获到异常时,由 WCF 服务向服务的使用者 Flex 或 Silverlight 等类型的客户端应用抛出原始或包装后的异常。这些异常会由 WCF 框架序列、反序列化并触发客户端的相关处理代码(当然,实际上这还涉及到 WCF 服务端 serviceDebug 的 includeExceptionDetailInFaults 参数是如何配置的细节)。这里想说接来的事,我们的客户端应用,如果在抛出异常的 WCF 服务客户端代理对象(client proxy object)上继续调用的话,那么就会发生这里说的“The communication object, System.ServiceModel.Channels.ServiceChannel, cannot be used for communication because it is in the Faulted state.”异常。实际上这种情况很容易发生,这与我们的设计有关。如果我们的客户端应用将 WCF 服务定位成 perSession 激活的生命周期形式的话(比如打算使用 wsHttpBinding 绑定提供的 reliableSession 支持等。当然了,如果客户端是 Flex 3 、Silverlight 2 应用的话,由于对 SOAP 1.2 版本支持的问题,我们必须使用 basicHttpBinding 绑定)。原因也很容易理解,WCF 服务端引发异常后,会使引用服务的客户端代理对象的 ServiceChannel 处于 Faulted 状态,在这种状态下 WCF 会要求重新打开服务的 Channel,也就是说我们在客户端关闭上次创建的服务代理对象,并再重新创建它,再通过调用 ChannelFactory.CreateChannel() 方法来打开 endpoint 通道。这里有个问题需要注意,我们如何订阅服务的 Faulted 事件?在我们引入 WCF 服务后,会在客户端创建服务本地代理对象,在 .NET Framework 中该对象会继承于 System.ServiceModel.ClientBase<IServerFoo>, IServerFoo,这里的 IServerFoo 是定义的服务契约接口;Flex 会继承于 AbstractWebService 类。它的 ChannelFactory 中有一个事件名为 Faulted,但事实证明如果直接订阅它的话,当调用 WCF 服务异常时,事件处理函数是不会被正确回调的。如果要截获该异常,我们需要在服务本地代理对象上的 ChannelFactory 成员对象上,在调用 CreateChannel() 方法所获得的 channel 对象上订阅 Faulted 事件。原因在于 Faulted 事件是发生在 channel 对象上的。具体编写代码时,这需要一个转形操作。可参考下面的 WCF 服务激动代码,在服务发生异常时,该代码会截获异常并重新创建服务、endpoint 通道。

 

public class CEELSBizSvcContext
{
	private static readonly string CEELSBizClientSessionKey = "CEELSBizClient";

	private static readonly string CEELSBizChannelSessionKey = "CEELSBizChannel";

	private static void CEELSBizChannelFactory_Faulted(object sender, EventArgs e)
	{
		CEELSBizClient ceelsBizClient = null;
		ICEELSBiz ceelsBizChannel = null;

		// Create CEELSBiz service in EACH session ONCE.
		// CEELSBiz WCF service use wsHttpBinding (soap12 version). InstanceContextMode is PerSession.
		HttpContext.Current.Session[CEELSBizClientSessionKey] = ceelsBizClient = new CEELSBizClient();

		HttpContext.Current.Session[CEELSBizChannelSessionKey] = ceelsBizChannel = ceelsBizClient.ChannelFactory.CreateChannel();

		(ceelsBizChannel as ICommunicationObject).Faulted += new EventHandler(CEELSBizChannelFactory_Faulted);

		// Auto login when CEELS service invoke faulted and restart.
		if (MembershipContext.IsAuthed)
			MembershipContext.Login(MembershipContext.AuthedUserNo,
				MembershipContext.AuthedUserPassword, MembershipContext.AuthedUserType);
	}

	public static ICEELSBiz Current
	{
		get
		{
			var ceelsBizSvcChannel = HttpContext.Current.Session[CEELSBizChannelSessionKey] as ICEELSBiz;

			if (ceelsBizSvcChannel == null)
			{
				CEELSBizChannelFactory_Faulted(null, EventArgs.Empty);

				ceelsBizSvcChannel = HttpContext.Current.Session[CEELSBizChannelSessionKey] as ICEELSBiz;
			}

			return ceelsBizSvcChannel;
		}
	}

	public static void Release()
	{
		var ceelsBizClient = HttpContext.Current.Session[CEELSBizClientSessionKey] as CEELSBizClient;

		if (ceelsBizClient != null)
			ceelsBizClient.Close();

		HttpContext.Current.Session[CEELSBizChannelSessionKey] = null;
		HttpContext.Current.Session[CEELSBizClientSessionKey] = null;
	}
}

 

          相信大家这么聪明,就不用再详细解释了吧。有了这层封装,通过 CEELSBizSvcContext.Current 属性客端所有的 WCF 服务调用代码都可以放心、方便的使用 CEELSBiz 服务了。

 

          这篇文章《Knowing when a WCF service in Fault state / Recover from fault state 》也对此进行了简要的介绍,可供参考。

 

// End ////

 

现有就到这里,后续再及时补充,同时也请大家多多分享。

 

2009-4-12 updated

 

作者:lzy.je
出处:http://lzy.iteye.com
本文版权归作者所有,只允许以摘要和完整全文两种形式转载,不允许对文字进行裁剪。未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

 

9
0
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics