Programming WCF Services翻译笔记(五)

 

本书的第3章主要讲解了有关数据契约的知识。“从抽象层面看,WCF能够托管CLR类型(接口和类)并将它们公开为服务,也能够以本地CLR接口和类的方式使用服务。WCF服务的操作接收和返回诸如int和string的CLR类型,WCF客户端则传递和处理返回的CLR类型。然而,CLR类型却属于.NET的特定技术。由于面向服务的一个核心原则就是在跨越服务边界时,服务不能够暴露它们的实现技术。因此,不管客户端采用了何种技术,它都能够与服务交互。显然,这就意味着WCF不允许在跨越服务边界时公开CLR数据类型。我们需要找到一种办法,实现CLR数据类型与标准的与平台无关的表示形式之间的转换。这样的表示形式就是基于XML的样式或信息集(Infoset)。此外,服务需要一种正式的方法声明两者之间的转换。这个方法就是本章所要介绍的主题——数据契约。本章的第一部分介绍了数据契约启用类型封送(Type Marshaling)与转换的方法,以及如何通过基础架构处理类的层级与数据契约的版本控制。第二部分则介绍了如何将不同的.NET类型,例如枚举、委托、数据表以及集合,作为数据契约使用。”

数据契约是服务之间传递的数据。由于必须支持跨进程,乃至于跨机器的传递,WCF必须对数据进行特殊的处理,否则无法实现数据的传递。在.NET Remoting与Web Service中,对数据的处理方式通常是利用序列化的方法,WCF同样沿袭了这一做法,但为了更好的体现面向服务的特质,又特别引入了数据契约(DataContract)。此外,WCF还引入了消息契约(MessageContract),但本书没有介绍。

WCF的序列化使用了.NET平台自身支持的序列化机制,因此这里不再重复。

.NET提供的序列化机制虽然足以应付SOA的要求,但仍然存在许多不足之处。本书总结了Serializable的缺陷:
“Serializable所指代的涵义是类型的所有成员都是可序列化的,这些成员是组成类型数据样式的一部分。然而,更好的方式是能够提供一种明确参与(Opt-In)途径,只有那些契约的开发者明确包含的成员才应该放到数据契约中。Serializable特性强制要求数据类型是可序列化的,从而使得类型可以被用作契约操作的参数,但它却无法实现类型的服务特性(具有成为WCF操作参数的能力)与序列化能力之间的职责分离。Serializalbe特性不支持类型名和成员名的别名,也无法将一个新类型映射为预定义的数据契约。由于Serializable特性可以直接操作成员字段,使得封装了字段访问的属性形同虚设。访问字段的最好办法是通过属性添加它们的值,而Serializable却破坏了属性的封装性。最后,Serializable特性并没有直接支持版本控制(Versioning),而版本控制的信息却是格式器期望获取的。无疑,它导致了版本控制的处理变得举步维艰。”

WCF提供的数据契约DataContract基本上解决了以上的问题。通常,DataContract必须与DataMember结合使用。只有应用了DataMember特性的属性才被公开到元数据中。虽然DataMember特性也可以应用到对象的字段上,但WCF并不推荐这样做,原因与类的设计原则相同。

数据契约与服务契约相似,数据成员或数据契约的访问限定与WCF之间并没有因果关系。数据契约完全可以包含私有数据成员等内部类型:
[DataContract]
struct Contact
{
   [DataMember]
   string m_FirstName;

   [DataMember]
   string m_LastName;
}

即使DataMember特性被直接应用到字段上,在导入的客户端定义仍然会以属性来表示。如下的数据契约定义:
[DataContract]
struct Contact
{
   [DataMember]
   public string FirstName;

   [DataMember]
   public string LastName;
}
导入的客户端定义为:
[DataContract]
public partial struct Contact
{
   string FirstNameField;
   string LastNameField;

   [DataMember]
   public string FirstName
   {
      get
      {
         return FirstNameField;
      }
      set
      {
         FirstNameField = value;
      }
   }

   [DataMember]
   public string LastName
   {
      get
      {
         return LastNameField;
      }
      set
      {
         LastNameField = value;
      }
   }
}
它会将字段名作为属性名,而导入的定义中,则在属性名后加上Field后缀作为字段名。但我们也可以手工修改客户端的定义。

如果数据契约的数据成员为私有的,导入的客户端定义会自动修改为公有的。“当DataMember特性应用到属性上时(不管是服务还是客户端),该属性必须具有get和set访问器。如果没有,在调用时就会抛出InvalidDataContractException异常。因为当属性自身就是数据成员时,WCF会在序列化和反序列化时使用该属性,使开发者能够将定制逻辑应用到属性中。”

“不要将DataMember特性既应用到属性上,又应用到相对应的字段上,这会导致导入的成员定义重复。”

如果服务端的数据被标记为Serializable特性,在导入这样的定义时,会使用DataContract。而且“对于每一个可序列化的成员,不管是公有的还是私有的,都是数据成员。” 

传统的格式器不能序列化只标记了DataContract特性的类型。要序列化这样的类型,必须同时应用DataContract特性和Serializable特性。对于如此类型生成的传输型表示形式(Wire Representation),就好似仅仅应用了DataContract特性一般,同时,我们仍然需要为成员添加DataMember特性。

在WCF的数据契约中,很明显地体现出WCF还不能够完全支持面向对象的设计思想。在第2章对服务契约的描述中,对契约的继承层级的处理方式来看,已经体现了这一缺陷的端倪。而对于数据契约而言,更是进一步暴露了这样的缺陷。

首先WCF并不支持Liskov替换原则(LSP),“默认情况下,我们不能用数据契约的子类去替换基类。” 考虑如下的服务契约:
[ServiceContract]
interface IContactManager
{
   //Cannot accept Customer object here:
   [OperationContract]
   void AddContact(Contact contact);

   //Cannot return Customer objects here:
   [OperationContract]
   Contact[] GetContacts(  );
}
假定客户端同时定义了一个Customer类:

[DataContract]
class Customer : Contact
{
   [DataMember]
   public int OrderNumber;
}
以下代码能够成功通过编译,但在运行时却会失败:

Contact contact = new Customer(  );
contact.FirstName = "Juval";
contact.LastName = "Lowy";

ContactManagerClient proxy = new ContactManagerClient(  );
//Service call will fail:
proxy.AddContact(contact);
proxy.Close(  );
因为在这个例子中,我们传递了一个Customer对象,而不是Contact对象。由于服务无法识别Customer对象,也就无法反序列化它所接收到的Contact对象。

虽然WCF引入了Known Types(已知类型)来解决这一问题,然而对于理解面向对象思想的设计者而言,这样的设计无疑会引入父类与子类之间的耦合度。因为在我们设计父类的时候,就必须事先知道子类的定义。当我们需要扩展子类时,还需要修改父类的定义。

WCF引入的服务已知类型,比较已知类型而言,有一定程度的改善。因为它可以将父类与子类在层级上的耦合度缩小到方法级上。但这样的耦合,依然是不可接受的。例如:
[DataContract]
class Contact
{...}

[DataContract]
class Customer : Contact
{...}
 
[ServiceContract]
interface IContactManager
{
   [OperationContract]
   [ServiceKnownType(typeof(Customer))]
   void AddContact(Contact contact);

   [OperationContract]
   Contact[] GetContacts(  );
}
当然,服务已知类型也可以应用到契约接口上,此时,该契约以及实现该契约的所有服务包含的所有操作都能够接收已知的子类。

为了解决这一问题,WCF提供了配置已知类型的方法。例如:
<system.runtime.serialization>
   <dataContractSerializer>
      <declaredTypes>
         <add type = "Contact,Host,Version=1.0.0.0,Culture=neutral,
                                                              PublicKeyToken=null">
            <knownType type = "Customer,MyClassLibrary,Version=1.0.0.0,
                                             Culture=neutral,PublicKeyToken=null"/>
         </add>
      </declaredTypes>
   </dataContractSerializer>
</system.runtime.serialization>

注意上述的配置文件中,我们配置的已知类型必须是类型的fullname。包括命名空间、版本号、Culture等。虽然这种方式可以避免在增加子类的情况下,修改代码、重新编译和重新部署,但无疑加重了开发者的负担,尤其是对配置文件的管理以及后期的维护。

不过,“如果已知类型对于另一个程序集而言是内部(internal)类型,要添加一个已知类型,只有使用配置文件声明它。”

总之,在WCF中要实现面向对象的多态,还未能做到最佳。如果能够将KnownType特性应用到子类上,为子类指名它所继承的父类,无疑更加利于类的扩展。遗憾的是WCF未能做到这一点。

如果数据契约本身实现了一个接口,情况就变得有趣了。从服务端的定义来看,这样的数据契约仍然可以通过服务已知类型在服务契约上指定实现了数据契约接口的子数据契约类型。例如,数据契约Contact类实现了接口IContact:
interface IContact
{  
   string FirstName
   {get;set;}
   string LastName
   {get;set;}
}
[DataContract]
class Contact : IContact
{...}

那么在处理数据契约Contact的服务契约中,如果契约的操作需要以抽象方式,定义IContact类型的参数,就必须使用ServiceKnownType特性指名其实现类Contact,如下所示:
[ServiceContract]
[ServiceKnownType(typeof(Contact))]
interface IContactManager
{
   [OperationContract]
   void AddContact(IContact contact);

   [OperationContract]
   IContact[] GetContacts(  );
}

注意,此时不能利用KnownType特性,将其直接应用到IContact接口上,因为导出的元数据无法包含接口本身。

服务端的定义无疑符合面向接口编程思想,除了增加了ServiceKnownType之外,整个设计还算优雅。然而根据这样的定义所导出的服务契约,却未免显得差强人意,如下所示:
[ServiceContract]
public interface IContactManager
{
    [OperationContract]
    [ServiceKnownType(typeof(Contact))]
    [ServiceKnownType(typeof(object[]))]
    void AddContact(object contact);
   
    [OperationContract]
    [ServiceKnownType(typeof(Contact))]
    [ServiceKnownType(typeof(object[]))]
    object[] GetContacts(  );
}

导出定义中,将应用到契约的ServiceKnownType特性应用到了每个操作上,并且为每个操作都指定了具体的数据契约子类以及一个object[]类型。特别要注意,在操作的返回值与参数中,原来的IContact类型全部被转换为了object类型。原因在于,客户端并没有IContact接口的定义。基于object的契约定义无疑不具备类型安全性。

解决办法自然是在客户端中增加IContact接口的定义。如此,客户端定义就可以修改为:
[ServiceContract]
public interface IContactManager
{
    [OperationContract]
    [ServiceKnownType(typeof(Contact))]
    void AddContact(IContact contact);
   
    [OperationContract]
    [ServiceKnownType(typeof(Contact))]
    IContact[] GetContacts(  );
}

但是,我们不能以具体的数据契约类型Contact,来替换原来的object类型。因为替换为具体的数据契约类型,则客户端的服务契约就与服务端的服务契约不兼容了。所以,下面的定义是错误的:
[ServiceContract]
public interface IContactManager
{
    [OperationContract]
    void AddContact(Contact contact);
   
    [OperationContract]
    Contact[] GetContacts(  );
}

 
以下是对提供的参考资料的总结,按照要求结构化多个要点分条输出: 4G/5G无线网络优化与网规案例分析: NSA站点下终端掉4G问题:部分用户反馈NSA终端频繁掉4G,主要因终端主动发起SCGfail导致。分析显示,在信号较好的环境下,终端可能因节能、过热保护等原因主动释放连接。解决方案建议终端侧进行分析处理,尝试关闭节电开关等。 RSSI算法识别天馈遮挡:通过计算RSSI平均值及差值识别天馈遮挡,差值大于3dB则认定有遮挡。不同设备分组规则不同,如64T和32T。此方法可有效帮助现场人员识别因环境变化引起的网络问题。 5G 160M组网小区CA不生效:某5G站点开启100M+60M CA功能后,测试发现UE无法正常使用CA功能。问题原因在于CA频点集标识配置错误,修正后测试正常。 5G网络优化与策略: CCE映射方式优化:针对诺基亚站点覆盖农村区域,通过优化CCE资源映射方式(交织、非交织),提升RRC连接建立成功率和无线接通率。非交织方式相比交织方式有显著提升。 5G AAU两扇区组网:与三扇区组网相比,AAU两扇区组网在RSRP、SINR、下载速率和上传速率上表现不同,需根据具体场景选择适合的组网方式。 5G语音解决方案:包括沿用4G语音解决方案、EPS Fallback方案和VoNR方案。不同方案适用于不同的5G组网策略,如NSA和SA,并影响语音连续性和网络覆盖。 4G网络优化与资源利用: 4G室分设备利旧:面对4G网络投资压减与资源需求矛盾,提出利旧多维度调优策略,包括资源整合、统筹调配既有资源,以满足新增需求和提质增效。 宏站RRU设备1托N射灯:针对5G深度覆盖需求,研究使用宏站AAU结合1托N射灯方案,快速便捷地开通5G站点,提升深度覆盖能力。 基站与流程管理: 爱立信LTE基站邻区添加流程:未提供具体内容,但通常涉及邻区规划、参数配置、测试验证等步骤,以确保基站间顺畅切换和覆盖连续性。 网络规划与策略: 新高铁跨海大桥覆盖方案试点:虽未提供详细内容,但可推测涉及高铁跨海大桥区域的4G/5G网络覆盖规划,需考虑信号穿透、移动性管理、网络容量等因素。 总结: 提供的参考资料涵盖了4G/5G无线网络优化、网规案例分析、网络优化策略、资源利用、基站管理等多个方面。 通过具体案例分析,展示了无线网络优化中的常见问题及解决方案,如NSA终端掉4G、RSSI识别天馈遮挡、CA不生效等。 强调了5G网络优化与策略的重要性,包括CCE映射方式优化、5G语音解决方案、AAU扇区组网选择等。 提出了4G网络优化与资源利用的策略,如室分设备利旧、宏站RRU设备1托N射灯等。 基站与流程管理方面,提到了爱立信LTE基站邻区添加流程,但未给出具体细节。 新高铁跨海大桥覆盖方案试点展示了特殊场景下的网络规划需求。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值