.NET中的SOAP自定义处理机制 (续二)

第二部分  SOAP消息头的处理


SOAP
消息头通常包含与消息体或SOAP处理(应用)方式相关的信息,比如消息路径、数字签名、认证信息,消息关联性信息、消息体的加密公匙等等。同第一部分一样我们可以利用属性机制来获取控制SOAP消息头的句柄,对SOAP消息头进行自定义,然后数据传递到Web services 方法或从Web services方法返回数据。消息头数据是不直接与Web services 方法的主功能相关的,因为它不是Web services方法的参数。

1.服务提供端的工作

我将以一个简单的认证服务作为例子,来讲解如何在程序中定义和使用自定义的消息头。首先我们所需要做的事情,就是在Web Service端从抽象类SoapHeader继承一个类(假设类名为AuthenticationHeader),用来表示SOAP消息头内的数据,注意这个类的类名将是SOAP消息头下的根元素(SOAP消息头的子元素)的名字(<soap:Header><AuthenticationHeader>…</AuthenticationHeader></soap:Header>),而类的公共成员变量名或属性名(假设一个属性名为AuthenticationToken)将是其下的子元素的名字(<soap:Header><AuthenticationHeader><AuthenticationToken>。。。</AuthenticationToken></AuthenticationHeader></soap:Header>)。对于客户端所发送的请求消息的消息头就需要遵守这个格式,向Web Service方法提供需要的信息。当Web Service方法接收到SOAP消息后,会进行并行化操作,根据定义新建一个SOAP头类对象,然后将SOAP消息头内的数据赋给该对象的相应属性,以便Web Service方法直接利用。以下就是该认证消息头类的主要代码:

     public class AuthenticationHeader : SoapHeader

     {   

         private string _AuthenticationToken = String.Empty;

         //该属性允许Web Service设置和获取SOAP HeaderAuthenticationToken元素的值

         public string AuthenticationToken

         {

              get {    return _AuthenticationToken;     }

              set {    //验证接收到的AuthenticationToken元素的值,如果为空则抛出异常

                       if (value == String.Empty)

                       {

                            throw new SoapException("No Authentication Token Received",null);

                       }

_AuthenticationToken = value;   

}

         }

     }

一旦定义好了消息头的格式,下一步的工作就是将SOAP消息头的数据与Web method关联起来。SoapHeader属性就是用来做这项工作的。首先需要向提供服务的WebService类添加一个公共成员变量(本例中为AuthHeader),这个变量的类型就是上面从SoapHeader类继承的那个类AuthenticationHeader。这里又引入了一个属性SoapHeader,这个SoapHeader实际上就是SoapHeaderAttributeSoapHeaderAttribute类的构造函数将头类变量的名字作为参数,并且加在了一个WebMethod的前面,这是为了将该SOAP头类与指定的Web Service方法关联起来,作为该WebMethod的一部分。下面是服务端WebService类的主要代码:

    public class SoapHeaderDemo : System.Web.Services.WebService

     {

         public AuthenticationHeader AuthHeader;

         // public ReceiptorHeader ReceiptHeader = new ReceiptorHeader();

        

         //SOAP header将确保每个用户都与一个Authenticationtoken相关联

         [WebMethod]

         [SoapHeader("AuthHeader", Direction=SoapHeaderDirection.In, Required=true)]

         // [SoapHeader("ReceiptHeader", Direction=SoapHeaderDirection.Out, Required=true)]

         public Account GetAccountDetails(int accountID)

         {

              //将认证标记传到Account的构造函数中,以便查询该标记对应的UserID。由于前面引入了// SoapHeader属性,这里就可以直接使用头类对象了

              Account ad = new Account(accountID,AuthHeader.AuthenticationToken);

              //如果UserIDAccountID都匹配,则说明认证成功,返回一个Account对象

              return ad;

         }

     }

 

请注意,这里没有创建SOAP头类对象AuthHeader的实例,因为AuthHeader处理的是来自客户端的SOAP消息头。由于SoapHeader属性的属性项Direction =  SoapHeaderDirection.InASP.NET运行时会在接收到来自客户端的消息后自动创建AuthHeader的实例,并用SOAP消息头内的数据对其相应属性赋值。如果WebService方法要向客户端发送SOAP消息头(假设根元素为ReceiptorHeader),则必须对该头类对象(假设SOAP头类对象为ReceiptHeader)实例化,且服务端对应的SoapHeader属性的属性项Direction=SoapHeaderDirection.Out

SOAP属性里有两个属性项DirectionRequiredDirection说明SOAP消息是从客户端发送到服务端(In)还是刚好相反(Out),或是两者都有(InOut) Required则指明消息头是否必须被包括在SOAP消息中。如果指定为True而没有包括,ASP.NET会抛出异常(SoapException,后面会讲到)。Required默认值为True,此时ASP.NET不支持HTTP GET/POST 绑定,也就是说不能通过ASP.NET自动生成的测试页来访问这个Web Method了。注意,“必须包含”并不表示“必须处理”,事实上完全有可能你向服务端发送了要求包含的SOAP消息头数据,而Web Service却并没有做任何处理(你会不会感觉有点“浪费表情”^_^),但这不会抛出任何异常。

另外,RequiredWeb service接口紧密相关,对这个属性的改变会影响到该Web serviceWSDL。如果Required设置为True,则在SOAP 绑定扩展性元素中定义的header元素的required属性也被设为true,同时在WSDL里会出现对消息头元素的Schema定义,客户端的SOAP消息头格式必须符合这个定义,否则服务端会抛出异常。

现在客户端传过来的SOAP消息头内的数据与Web Method已经关联起来了,并可直接通过头类对象访问,其余的工作就是完善验证和数据处理功能的代码,这里就不再熬述了。

2.客户端的工作

上面提到的是服务端的处理,那客户端如何在调用Web Method时将消息头加进去呢?方法就是通过客户端代理类创建WebService端消息头类(本例为AuthenticationHeader)对象的实例,将需要发送的数据赋到相应的公共成员变量里去,再调用Web Method就行了。下面给出一个在请求消息中添加信息头的例子:

 

//从本地取得用户的认证标记

_authToken = (string)this.ViewState["AuthToken"];

//生成代理类对象

SoapHeaderClient.localhost.SoapHeaderDemo demo =

                   new SoapHeaderClient.localhost.SoapHeaderDemo();

//生成代理头类对象

demo.AuthenticationHeaderValue  =

                   new SoapHeaderClient.localhost.AuthenticationHeader();

//将认证标记添加到代理头类对象的相应元素中

demo.AuthenticationHeaderValue.AuthenticationToken = _authToken;

 

3SOAP Header的属性

但是这里还有个问题,客户端向服务端发送的SOAP消息头除了Web Method能够处理的以外,可能还包括了一些Web Method不能识别(更不能处理)的SOAP消息头。更可怕的是客户端甚至可能将这些服务端不能识别的SOAP消息头所对应的SOAP头类对象的MustUnderstand属性设置为true,这样可就麻烦了。呵呵,幸运的是.NET还提供了一种机制专门解决这种不明SOAP消息头的处理问题。首先我们还是来了解一下MustUnderstand属性吧。

MustUnderstand属性来自于SoapHeader类,它与前面讲到的SoapHeaderAttribute类的Required属性完全不同,它表示消息的接收者是否必须理解(处理)这个指定的消息头。

DidUnderstand也是SoapHeader类的一个属性,在Web method中可以通过设置某个头类对象的DidUnderstand属性的值,来告诉客户端哪些信息头已经被处理,哪些没有处理。对于由Web method定义的消息头,DidUnderstand的默认值为true。需要指出的是,对于客户端mustUnderstand属性设置为True而在Web method中却没有处理的消息头,要么将DidUnderstand属性的值设为False,要么就主动抛出一个异常,否则就会出错!在Web Method返回前,.NET会检查所有客户端传过来的SoapHeader,一旦发现客户端将mustUnderstand属性设置为True,而对应的DidUnderstand属性却为False,就会抛出SoapHeaderException异常。

针对上面SOAP消息头可能没有处理的情况,可以在Web method的开始部分将所有SoapHeaderDidUnderstand属性设置为False,一旦某个SoapHeader被处理了,就将其DidUnderstand设置为True。或者在Web method决定是否对某个SoapHeader进行处理时,将客户端设置的MustUnderstand属性的值作为一个判断条件。

4.不明SOAP消息头的处理

.NET提供了一种机制来处理没有被Web method正式定义的Soap Header(SoapUnknownHeader)SoapUnknownHeader类继承自SoapHeader,因此也有 MustUnderstandDidUnderstand属性。SoapUnknownHeader类型的对象是松散的,它自己定义的属性只有ElementXmlElement类型),Element属性用来访问不明Soap头类的根元素,可以通过它来遍历该头类的所有元素的内容。

和其它SoapHeader类一样,可以通过SoapHeader属性将SoapUnknownHeaderWeb method关联起来。如果有不只一个SoapUnknownHeaderSoapHeader属性的MemberName属性项就是一个数组。下面是一个处理不明SOAP消息头例子:

public class MyWebService

{

     public MyHeader myHeader;

     // 声明数组准备接收所有的不明SOAP消息头

     public SoapUnknownHeader[] unknownHeaders;

 

     [WebMethod]

     [SoapHeader ("myHeader", Direction=SoapHeaderDirection.InOut,Required=true)]

     //通过SoapHeader属性,接受所有不明SOAP消息头

     [SoapHeader ("unknownHeaders", Required=false)]

 

     public string MyWebMethod()

     {

         string unknownHeaderAttributes = String.Empty;

         // 处理已知消息头myHeader,过程略

         // 检查每个不明SOAP消息头

         foreach (SoapUnknownHeader header in unknownHeaders)

         {

              // 列出每个不明SOAP消息头的根元素的每个属性名值对

              foreach (XmlAttribute attribute in header.Element.Attributes)

              {

                   unknownHeaderAttributes = unknownHeaderAttributes + attribute.Name + ":" +                    attribute.Value + ";";

              }

              // 告诉客户端,这些不明SOAP消息头不能够处理

              header.DidUnderstand = false;

         }

         return unknownHeaderAttributes;

     }

}

对于这个例子,如果客户端要求必须对某个不明SOAP消息头进行处理,在Web Method返回时会自动抛出异常(或主动抛出异常,详细描述),客户端可以根据异常原因来决定进一步的操作。

5SOAP异常

当客户端使用SOAP进行调用时,由于各种各样的原因Web Services方法可能会引发SoapException(继承自Exception类),比如名称空间不匹配、SOAP头未处理(此时引发SoapHeaderException)、编码格式不被识别、数据库处理错误、主动抛出异常等等。异常在服务器上被捕获并包装在一个新的SoapException实例内,然后写入SOAP体(Body)的Fault元素中,作为响应返回给客户端。下面是一个返回异常的SOAP消息体:

<soap:Body>

<soap:Fault>

<faultcode>soap:Server</faultcode> --错误码

<faultstring>System.Web.Services.Protocols.SoapHeaderException: 服务器在消息中未找到所需的 AuthenticationHeader SOAP 标头。

   at System.Web.Services.Protocols.SoapHeaderHandling.SetHeaderMembers(SoapHeaderCollection headers, Object target, SoapHeaderMapping[] mappings, SoapHeaderDirection direction, Boolean client)

   at System.Web.Services.Protocols.SoapServerProtocol.CreateServerInstance()

   at System.Web.Services.Protocols.WebServiceHandler.Invoke()

   at System.Web.Services.Protocols.WebServiceHandler.CoreProcessRequest()

</faultstring> --错误内容

<detail />     --在抛出异常时自定义的详细错误信息,这里为空

</soap:Fault>

</soap:Body>

 

前面说过.NET会自动根据错误原因和类型生成相应的异常,同时Web Services也可以根据需要人工抛出异常,不管是SoapException,还是普通的Exception,例如:

SoapException se = new SoapException("Fault occurred", SoapException.ClientFaultCode,Context.Request.Url.AbsoluteUri,node);

throw se;

throw new Exception("Fault occurred");

而客户端可以通过下面的方法来获取关于异常的详细信息:

try

{ }

catch (Exception e)

{

Console.WriteLine(e.Source);

Console.WriteLine(e.Message);

//SoapException的属性

Console.WriteLine(e.Code);

Console.WriteLine(e.Actor);

Console.WriteLine(e.Detail);

}

posted on 2004-09-28 11:31  abedon  阅读(2825)  评论(1编辑  收藏  举报

导航