在日常的開發過程中,我們會經常迭代發布不同的版本,所以WCF服務的接口也會經常處於變動的狀態,比如在傳遞實體類中新加一個字段、修改參數名稱等等關於服務升級的問題。但是我們不可能讓已發布的版本重新引用新的服務,這是不現實的,所以我們在升級WCF服務時,一定要讓服務兼容以前的版本。現在我們分別介紹關於服務升級的幾個常用情況。
一、參數變動
我們來實現最初的版本1.0,新建一個服務接口,服務實現很簡單,在Output窗口中簡單輸出服務接收到的參數值。
[ServiceContract] public interface ITestingService { [OperationContract] void AddUser(string id, string username, int age); }
public class TestingService : ITestingService { public void AddUser(string id, string username, int age) { System.Diagnostics.Debug.WriteLine(string.Format("id = {0}", id)); System.Diagnostics.Debug.WriteLine(string.Format("username = {0}", username)); System.Diagnostics.Debug.WriteLine(string.Format("age = {0}", age)); } }
在客戶端引用1.0版本的服務,調用服務接口
class Program { static void Main(string[] args) { var testingServiceClient = new TestingServiceClient(); testingServiceClient.AddUser("001", "James", 18); Console.ReadKey(); } }
在服務端的Output輸出結果如下
1.添加參數
考慮一種特別常用的情形,我需要添加一個新的字段,以便得到更多的信息,我們需要升級服務至2.0,所以我們需要修改接口和實現。最終的結果當然是不影響引用1.0服務的客戶端的繼續使用。
讓我們修改接口和實現。
[ServiceContract] public interface ITestingService { [OperationContract] void AddUser(string id, string username, int age, string city); }
public class TestingService : ITestingService { public void AddUser(string id, string username, int age, string city) { System.Diagnostics.Debug.WriteLine(string.Format("id = {0}", id)); System.Diagnostics.Debug.WriteLine(string.Format("username = {0}", username)); System.Diagnostics.Debug.WriteLine(string.Format("age = {0}", age)); System.Diagnostics.Debug.WriteLine(string.Format("city = {0}", city)); } }
首先我們需要保證引用1.0服務的客戶端的繼續使用,所以我們先測試引用1.0服務的客戶端。
從測試結果可以看到,在引用舊的服務的客戶端在調用新的服務時,可以正常調用,只是新添加的字段是默認值。這是正確的結果,因為在舊的客戶端傳遞過來的數據中不包含新添加的字段的信息,自然新添加的字段的值是默認的值。
2.刪除不再需要的參數
緊接1.0版本,假設在新的2.0版本中不再需要age的值,所以需要在接口中刪除這個參數,所以需要修改接口和實現。
[ServiceContract] public interface ITestingService { [OperationContract] void AddUser(string id, string username); }
public class TestingService : ITestingService { public void AddUser(string id, string username) { System.Diagnostics.Debug.WriteLine(string.Format("id = {0}", id)); System.Diagnostics.Debug.WriteLine(string.Format("username = {0}", username)); } }
首先我們需要保證引用1.0服務的客戶端的繼續使用,所以我們先測試引用1.0服務的客戶端。
從測試結果可以看到,在引用舊的服務的客戶端在調用新的服務時,可以正常調用。
3.修改參數名(重構)
緊接1.0版本,假設在新的2.0版本中需要修改username名為name,所以需要修改接口和實現。
[ServiceContract] public interface ITestingService { [OperationContract] void AddUser(string id, string name); }
public class TestingService : ITestingService { public void AddUser(string id, string name) { System.Diagnostics.Debug.WriteLine(string.Format("id = {0}", id)); System.Diagnostics.Debug.WriteLine(string.Format("username = {0}", name)); } }
在保證客戶端不引用新的服務的前提下,我們測試客戶端的服務調用情況。
從結果可以看到,如果修改了參數的名稱則會影響到舊版本客戶端的使用,難道沒有別的什么方法可以解決這個問題么?答案是有的,如果想要重構但是不想改動客戶端代碼的話,那么可以給參數加上一個MessageParameter的屬性,代碼如下:
[ServiceContract] public interface ITestingService { [OperationContract] void AddUser(string id, [MessageParameter(Name = "username")]string name); }
重新測試舊版本的客戶端,可以看到最終的結果又恢復正常。
二、實體的變動
在第一部分我們談論的是參數的變動,在第二部分我們變化實體中屬性的變動。先來定義最初的1.0服務版本。
[DataContract] public class User { [DataMember] public string Id { get; set; } [DataMember] public string Name { get; set; } [DataMember] public int Age { get; set; } }
[ServiceContract] public interface ITestingService { [OperationContract] void AddUser(User user); }
public class TestingService : ITestingService { public void AddUser(User user) { System.Diagnostics.Debug.WriteLine(string.Format("id = {0}", user.Id)); System.Diagnostics.Debug.WriteLine(string.Format("username = {0}", user.Name)); System.Diagnostics.Debug.WriteLine(string.Format("age = {0}", user.Age)); } }
客戶端引用最初的服務
class Program { static void Main(string[] args) { var testingServiceClient = new TestingServiceClient(); var user = new User() { Id = "001", Name = "Tommy", Age = 25}; testingServiceClient.AddUser(user); Console.ReadKey(); } }
測試結果如下
1.在實體類中添加屬性
在User類中添加City屬性,以便在新的服務版本中獲取更多的用戶信息。服務接口不變,只需修改實體類和服務實現。
[DataContract] public class User { [DataMember] public string Id { get; set; } [DataMember] public string Name { get; set; } [DataMember] public int Age { get; set; } [DataMember] public string City { get; set; } }
public void AddUser(User user) { System.Diagnostics.Debug.WriteLine(string.Format("id = {0}", user.Id)); System.Diagnostics.Debug.WriteLine(string.Format("username = {0}", user.Name)); System.Diagnostics.Debug.WriteLine(string.Format("age = {0}", user.Age)); System.Diagnostics.Debug.WriteLine(string.Format("city = {0}", user.City)); }
在保證客戶端不引用新的服務的前提下,測試客戶端的服務調用情況。從結果可以看出,舊的客戶端可以正常調用新的服務,只是新添加的字段沒有顯式賦值。
2.在實體類中刪除屬性
在User類中刪除Age屬性。服務接口不變,只需修改實體類和服務實現。
[DataContract] public class User { [DataMember] public string Id { get; set; } [DataMember] public string Name { get; set; } }
public class TestingService : ITestingService { public void AddUser(User user) { System.Diagnostics.Debug.WriteLine(string.Format("id = {0}", user.Id)); System.Diagnostics.Debug.WriteLine(string.Format("username = {0}", user.Name)); } }
在保證客戶端不引用新的服務的前提下,測試客戶端的服務調用情況。從結果可以看出,舊的客戶端可以正常調用新的服務。
3.在實體類中修改屬性名
在User類中修改Name屬性的名稱為UserName。服務接口不變,只需修改實體類和服務實現。
[DataContract] public class User { [DataMember] public string Id { get; set; } [DataMember] public string UserName { get; set; } [DataMember] public int Age { get; set; } }
public class TestingService : ITestingService { public void AddUser(User user) { System.Diagnostics.Debug.WriteLine(string.Format("id = {0}", user.Id)); System.Diagnostics.Debug.WriteLine(string.Format("username = {0}", user.UserName)); } }
在保證客戶端不引用新的服務的前提下,測試客戶端的服務調用情況。
從結果可以看到,如果修改了實體屬性的名稱則會影響到舊版本客戶端的使用,可以在DataMemeber中設定Name屬性的值,代碼如下
[DataMember(Name = "Name")] public string UserName { get; set; }
重新測試客戶端,發現可以繼續正常使用。