傳統 WCF 開發依賴 svcutil.exe 或 Visual Studio「加入服務參考」產生 Proxy 類別,導致服務端精心設計的領域模型 (不可變物件、驗證邏輯、計算屬性、特定集合類型) 在客戶端退化為單純的 DTO (Data Transfer Object)。本文展示如何繞過代理生成,直接共享 服務合約介面與資料契約類別,並構建無 Proxy 的 ClientBase<T> 衍生類別,同時支援單元測試依賴注入。
領域模型常將屬性設為 private set 確保不可變性,但代理生成工具會產生可讀寫屬性並實作 INotifyPropertyChanged,破壞領域模型完整性。共享原始類別可保留 private set 與建構函式驗證邏輯。
[DataContract(Namespace = "https://zsr.codeplex.com/services/")]
public class ZombieTypeSummary {
public ZombieTypeSummary(string zombieTypeName, int zombieTypeKey, string briefDescription = null, Uri thumbnailImage = null) {
ZombieTypeName = zombieTypeName; ZombieTypeKey = zombieTypeKey; BriefDescription = briefDescription; ThumbnailImage = thumbnailImage;
}
[Obsolete("This is only used by the DataContractSerializer", true)]
public ZombieTypeSummary() { } // 僅供序列化器使用
[DataMember] public string ZombieTypeName { get; private set; }
[DataMember] public int ZombieTypeKey { get; private set; }
[DataMember] public string BriefDescription { get; private set; }
[DataMember] public Uri ThumbnailImage { get; private set; }
}
[Required]) 或計算屬性。partial 類別分離手寫邏輯,但無法攔截屬性 setter (缺少 XxxChanging/Changed 部分方法)。代理工具將所有集合統一為 IList<T>,喪失 ObservableCollection<T>、BindingList<T> 等具體類型,影響 UI 綁定與變更通知。
代理生成需服務端運行,難以納入自動化建置,導致開發者手動更新、版本不一致。
using System.ServiceModel;
using System.ServiceModel.Channels;
namespace Zombie.Services.Definitions {
public class InformationClient : ClientBase<IInformationService> {
public new IInformationService Channel { get { return base.Channel; } }
public InformationClient() { }
public InformationClient(string endpointConfigurationName) : base(endpointConfigurationName) { }
public InformationClient(string endpointConfigurationName, string remoteAddress) : base(endpointConfigurationName, remoteAddress) { }
public InformationClient(string endpointConfigurationName, EndpointAddress remoteAddress) : base(endpointConfigurationName, remoteAddress) { }
public InformationClient(Binding binding, EndpointAddress remoteAddress) : base(binding, remoteAddress) { }
}
}
private IInformationService _mockService;
public InformationClient(IInformationService mockService) : base(new BasicHttpBinding(), new EndpointAddress("https://fakeAddress.com")) {
_mockService = mockService;
}
public new IInformationService Channel { get { return _mockService ?? base.Channel; } }
protected override IInformationService CreateChannel() { return _mockService ?? base.CreateChannel(); }
public new void Open() { if (_mockService == null) base.Open(); }
測試時傳入 Mock 物件,生產環境使用真實端點。注意:此模式非完全封裝,已知限制為可轉型為基類呼叫真實 Open(),但配合假地址可防誤連線。
當服務端與客戶端 (如 .NET 與 WinRT) 僅需共享類別部分邏輯:
#if CLIENT 區隔平台差異。優點:同步容易;缺點:雜亂。ZombieTypeSummary.shared.cs),平台特有邏輯放入各自專案的 ZombieTypeSummary.client.cs / .server.cs。優點:結構清晰;缺點:需維護多檔案。Silverlight WCF 僅支援 APM 模式 (Begin/End),需將 Task 介面包裝:
[ServiceContract(Namespace = "...")]
public interface IInformationService {
[OperationContract(AsyncPattern = true)] IAsyncResult BeginListZombieTypes(AsyncCallback cb, object state);
ZombieTypeSummaryCollection EndListZombieTypes(IAsyncResult result);
// ...
}
public static class InformationServiceExtensions {
public static Task<ZombieTypeSummaryCollection> ListZombieTypes(this IInformationService client) {
return Task.Factory.FromAsync(client.BeginListZombieTypes, client.EndListZombieTypes, null);
}
// ...
}
共享服務合約與資料契約組件,配合 ClientBase<T> 衍生類別,能消除 Proxy 產生的諸多弊端:保留領域模型豐富性、集合類型精確性、建置自動化,並自然支援單元測試。此模式適用於 .NET 全平台 (含 Silverlight/WinRT),是現代 WCF 開發的推薦實踐。專案完整碼託管於 CodePlex - Zombie Standard Reference。