WCF开发之契约的版本控制

通常来说有两种控制方式:

• 严格的版本控制:
– 对于服务或者数据契约的任何修改都需要形成独立
的新版本(formal versioning)
• 实用的版本控制:
– 对于向后和向前同时兼容的版本相容性– 服务与数
据契约
– 保存未知元素(如果可行的话)
– 容忍缺失的元素
– 只有当签名发生显著变化并无法和老的版本相容时才需要形成独立的新版本

模拟一个场景:

• ServiceA上的版本1.0的服务契约
• 两个操作:1,2

WCF开发之契约的版本控制_第1张图片

 

非严格的方法
– 在现有的契约上添加新的方法
– 相同的命名空间
– 更新客户端到最新的版本

WCF开发之契约的版本控制_第2张图片

WCF开发之契约的版本控制_第3张图片

 

半严格的方法1
– 在使用新的命名空间的新的契约上添加新的方法
– 继承自旧的契约
– 在原有的端点上暴露新的契约

WCF开发之契约的版本控制_第4张图片

半严格的方法2
– 在使用新的命名空间的新的契约上添加新的方法
– 继承自旧的契约
– 在新的端点上暴露新的契约

WCF开发之契约的版本控制_第5张图片

WCF开发之契约的版本控制_第6张图片

 

独立的新版本控制
– 为所有的操作在新的命名空间下创建新的契约
– 没有继承关系
– 独立创建服务和端点
– 共享基类服务类型中的公共函数

WCF开发之契约的版本控制_第7张图片

WCF开发之契约的版本控制_第8张图片

下面通过一个具体的Demo1来体会一下通过继承处理的版本控制的方法:

初始的程序很简单,一个Service一个Client,主要代码如下:

Service代码:

IServiceA代码
using  System;
using  System.Collections.Generic;
using  System.Linq;
using  System.Runtime.Serialization;
using  System.ServiceModel;
using  System.Text;

namespace  BusinessServices
{
    
//  NOTE: If you change the interface name "IService1" here, you must also update the reference to "IService1" in App.config.
    [ServiceContract]
    
public   interface  IMyServiceA
    {
        [OperationContract]
        
string  Operation1();
        [OperationContract]
        
string  Operation2();
    }
}

 

 

MyService代码
using  System;
using  System.Collections.Generic;
using  System.Linq;
using  System.Runtime.Serialization;
using  System.ServiceModel;
using  System.Text;

namespace  BusinessServices
{
    
//  NOTE: If you change the class name "Service1" here, you must also update the reference to "Service1" in App.config.
     public   class  MyService : IMyServiceA
    {
        
public   string  Operation1()
        {
            
return   " IMyServiceA.Operation1() is invoked. " ;
        }

        
public   string  Operation2()
        {
            
return   " IMyServiceA.Operation2() is invoked. " ;
        }
    }
}

 

 

App.config代码
<? xml version = " 1.0 "  encoding = " utf-8 "   ?>
< configuration >
  
< system.web >
    
< compilation debug = " true "   />
  
</ system.web >
  
<!--  When deploying the service library project, the content of the config file must be added to the host '
  app.config file. System.Configuration does not support config files  for  libraries.  -->
  
< system.serviceModel >
    
< services >
      
< service behaviorConfiguration = " BusinessServices.Service1Behavior "
        name
= " BusinessServices.MyService " >
        
< endpoint address = ""  binding = " wsHttpBinding "  contract = " BusinessServices.IMyServiceA " >
          
< identity >
            
< dns value = " localhost "   />
          
</ identity >
        
</ endpoint >
        
< endpoint address = " mex "  binding = " mexHttpBinding "  contract = " IMetadataExchange "   />
        
< host >
          
< baseAddresses >
            
< add baseAddress = " http://localhost:8731/WCF/Charlesliu "   />
          
</ baseAddresses >
        
</ host >
      
</ service >
    
</ services >
    
< behaviors >
      
< serviceBehaviors >
        
< behavior name = " BusinessServices.Service1Behavior " >
          
<!--  To avoid disclosing metadata information, 
          
set  the value below to  false  and remove the metadata endpoint above before deployment  -->
          
< serviceMetadata httpGetEnabled = " True " />
          
<!--  To receive exception details  in  faults  for  debugging purposes, 
          
set  the value below to  true .  Set to  false  before deployment 
          to avoid disclosing exception information 
-->
          
< serviceDebug includeExceptionDetailInFaults = " False "   />
        
</ behavior >
      
</ serviceBehaviors >
    
</ behaviors >
  
</ system.serviceModel >
</ configuration >

 

Client代码:

WinClientV1代码
using  System;
using  System.Collections.Generic;
using  System.ComponentModel;
using  System.Data;
using  System.Drawing;
using  System.Linq;
using  System.Text;
using  System.Windows.Forms;

namespace  WinClientV1
{
    
public   partial   class  Form1 : Form
    {
        MyServiceReference.MyServiceAClient proxy 
=   new  WinClientV1.MyServiceReference.MyServiceAClient();

        
public  Form1()
        {
            InitializeComponent();
        }

        
private   void  btnOperation1_Click( object  sender, EventArgs e)
        {
            MessageBox.Show(proxy.Operation1());
        }

        
private   void  btnOperation2_Click( object  sender, EventArgs e)
        {
            MessageBox.Show(proxy.Operation2());
        }
    }
}

 

程序很简单,主要实现了调用服务并显示调用的是那个方法。

好了现在有新的需求来了,需要添加一个新的方法Operation3,我们通过继承来实现,

首先创建一个新的IMyServiceA2接口程序,使得他继承自IMyServiceA:

 

IMyServiceA2代码
using  System;
using  System.Collections.Generic;
using  System.Linq;
using  System.Runtime.Serialization;
using  System.ServiceModel;
using  System.Text;

namespace  BusinessServices
{
    
//  NOTE: If you change the interface name "IService1" here, you must also update the reference to "IService1" in App.config.
    [ServiceContract]
    
public   interface  IMyServiceA2 : IMyServiceA
    {
        [OperationContract]
        
string  Operation3();
    }
}

 

然后修改Service.cs,让他实现IMyServiceA2:

MyService 代码
using  System;
using  System.Collections.Generic;
using  System.Linq;
using  System.Runtime.Serialization;
using  System.ServiceModel;
using  System.Text;

namespace  BusinessServices
{
    
//  NOTE: If you change the class name "Service1" here, you must also update the reference to "Service1" in App.config.
     public   class  MyService : IMyServiceA2
    {
        
public   string  Operation1()
        {
            
return   " IMyServiceA.Operation1() is invoked. " ;
        }

        
public   string  Operation2()
        {
            
return   " IMyServiceA.Operation2() is invoked. " ;
        }

        
public   string  Operation3()
        {
            
return   " IMyServiceA.Operation3() is invoked. " ;
        }
    }
}

 

然后修改app.config到IServiceA2

 

App.config代码
<? xml version = " 1.0 "  encoding = " utf-8 "   ?>
< configuration >
  
< system.web >
    
< compilation debug = " true "   />
  
</ system.web >
  
<!--  When deploying the service library project, the content of the config file must be added to the host '
  app.config file. System.Configuration does not support config files  for  libraries.  -->
  
< system.serviceModel >
    
< services >
      
< service behaviorConfiguration = " BusinessServices.Service1Behavior "
        name
= " BusinessServices.MyService " >
        
< endpoint address = ""  binding = " wsHttpBinding "  contract = " BusinessServices.IMyServiceA2 " >
          
< identity >
            
< dns value = " localhost "   />
          
</ identity >
        
</ endpoint >
        
< endpoint address = " mex "  binding = " mexHttpBinding "  contract = " IMetadataExchange "   />
        
< host >
          
< baseAddresses >
            
< add baseAddress = " http://localhost:8731/WCF/Charlesliu "   />
          
</ baseAddresses >
        
</ host >
      
</ service >
    
</ services >
    
< behaviors >
      
< serviceBehaviors >
        
< behavior name = " BusinessServices.Service1Behavior " >
          
<!--  To avoid disclosing metadata information, 
          
set  the value below to  false  and remove the metadata endpoint above before deployment  -->
          
< serviceMetadata httpGetEnabled = " True " />
          
<!--  To receive exception details  in  faults  for  debugging purposes, 
          
set  the value below to  true .  Set to  false  before deployment 
          to avoid disclosing exception information 
-->
          
< serviceDebug includeExceptionDetailInFaults = " False "   />
        
</ behavior >
      
</ serviceBehaviors >
    
</ behaviors >
  
</ system.serviceModel >
</ configuration >

 

此时我们启动ServiceA2和WinClient1,发现老的客户端仍然可以正常使用。原因是因为A2是继承自A的,所以A2只是A的扩展,并没有改变A的契约,而且A2是包含了A的契约的,所以A的老客户端可以正常使用。

现在我们想要为A2发布一个新的客户端WinClientV2:

 

WinClientV2代码
using  System;
using  System.Collections.Generic;
using  System.ComponentModel;
using  System.Data;
using  System.Drawing;
using  System.Linq;
using  System.Text;
using  System.Windows.Forms;

namespace  WinClientV2
{
    
public   partial   class  Form1 : Form
    {
        MyServiceReference.MyServiceA2Client proxy 
=   new  WinClientV2.MyServiceReference.MyServiceA2Client();

        
public  Form1()
        {
            InitializeComponent();
        }

        
private   void  btnOperation1_Click( object  sender, EventArgs e)
        {
            MessageBox.Show(proxy.Operation1());
        }

        
private   void  btnOperation2_Click( object  sender, EventArgs e)
        {
            MessageBox.Show(proxy.Operation2());
        }

        
private   void  btnOperation3_Click( object  sender, EventArgs e)
        {
            MessageBox.Show(proxy.Operation3());
        }
    }
}

 

WinClientV2当然可以正常使用。

上面的例子实现了通过继承方式来扩展老的服务,并且不影响老的客户端的使用,在实际应用中这种情况非常多。上面的例子有一点要提一下:

Client1中的代理类中的Action如下:

http://tempuri.org/IMyServiceA/Operation1
http://tempuri.org/IMyServiceA/Operation12

Client2中的代理类中的Action如下:

http://tempuri.org/IMyServiceA/Operation1
http://tempuri.org/IMyServiceA/Operation2
http://tempuri.org/IMyServiceA2/Operation3

可以看出来Client实际上是同多Action来调用不同的Operation的。

上面使用继承方式实现的,也可以不用,另一种方法是一个Host提供2个服务,客户端各调各的。

 

ServiceA代码
using  System;
using  System.ServiceModel;

namespace  BusinessServiceContracts
{

    [ServiceContract(Name
= " ServiceAContract " , Namespace  =   " http://www.thatindigogirl.com/samples/2006/06 " )]
    
public   interface  IServiceA
    {
        [OperationContract]
        
string  Operation1();
        [OperationContract]
        
string  Operation2();
    }

    
public   class  ServiceA : IServiceA
    {

        
public   string  Operation1()
        {
            
return   " IServiceA.Operation1() invoked. " ;
        }

        
public   string  Operation2()
        {
            
return   " IServiceA.Operation2() invoked. " ;
        }

    }
}

 

 

 

IServiceA2代码
using  System;
using  System.ServiceModel;

namespace  BusinessServiceContracts
{

    [ServiceContract(Name
= " ServiceAContract2 " , Namespace  =   " http://www.thatindigogirl.com/samples/2006/08 " )]
    
public   interface  IServiceA2
    {
        [OperationContract]
        
string  Operation1();
        [OperationContract]
        
string  Operation2();
        [OperationContract]
        
string  Operation3();
    }

    
public   class  ServiceA2 : IServiceA2
    {

        
public   string  Operation1()
        {
            
return   " IServiceA2.Operation1() invoked. " ;
        }

        
public   string  Operation2()
        {
            
return   " IServiceA2.Operation2() invoked. " ;
        }

        
public   string  Operation3()
        {
            
return   " IServiceA2.Operation3() invoked. " ;
        }
    }
}

 

值得注意的是app.config

 

Config代码
<? xml version="1.0" encoding="utf-8"  ?>
< configuration >
    
< system.serviceModel >
    
< behaviors >
      
< serviceBehaviors >
        
< behavior  name ="serviceBehavior" >
          
< serviceMetadata  httpGetEnabled ="true" />
        
</ behavior >
      
</ serviceBehaviors >
    
</ behaviors >
    
< services >
            
< service  name ="BusinessServices.ServiceA"  behaviorConfiguration ="serviceBehavior" >
        
< host >
          
< baseAddresses >
            
< add  baseAddress ="http://localhost:8000" />
          
</ baseAddresses >
        
</ host >
        
< endpoint  address ="ServiceA"  contract ="BusinessServiceContracts.IServiceA"  binding ="basicHttpBinding"    />
        
< endpoint  contract ="IMetadataExchange"  binding ="mexHttpBinding"  address ="mex"   />
        
      
</ service >
        
< service  name ="BusinessServices.ServiceA2"  behaviorConfiguration ="serviceBehavior" >
          
< host >
            
< baseAddresses >
              
< add  baseAddress ="http://localhost:8001" />
            
</ baseAddresses >
          
</ host >
          
< endpoint  address ="ServiceA2"  contract ="BusinessServiceContracts.IServiceA2"  binding ="basicHttpBinding"    />
          
< endpoint  contract ="IMetadataExchange"  binding ="mexHttpBinding"  address ="mex"   />

        
</ service >
      
</ services >
    
</ system.serviceModel >
</ configuration >

 

 

关于数据契约的版本控制和上面的服务契约的版本控制相似:

• 非严格方法:
– 不要删除required的成员
– 添加新的non-required成员
– 不改变命名空间

WCF开发之契约的版本控制_第9张图片

• 形成独立的新版本:
– 根据业务逻辑和数据层的需要,更新业务逻辑对象(保留类型名称)
– 管理数据契约命名空间的版本
– 管理服务契约的版本
WCF开发之契约的版本控制_第10张图片

• 对于1.0版本的客户端:
– 在原始的数据契约命名空间上创建旧的业务逻辑对象(新的类型名称)的副本
– 保留1.0版本的服务端点(endpoint)
– 在调用业务逻辑层之间映射到2.0的版本上

1.0版本的数据契约

WCF开发之契约的版本控制_第11张图片

支持1.0版本和2.0版本的数据契约

WCF开发之契约的版本控制_第12张图片

将1.0版本映射到2.0版本的数据契约上

WCF开发之契约的版本控制_第13张图片

总结

• WCF缺省提供版本相容性支持
• 非严格的版本控制方法降低了开发耗费
• 严格的版本控制方法需要有远见和计划性
• 尽早地考虑契约版本控制策略以应对一些无法避免的变化 (完)

你可能感兴趣的:(版本控制)