.NET、.NET Compact Framework 和 Silverlight 的 RESTful 无代理 WCF 客户端
适用于三个不同平台的示例 WCF 客户端,无需代码重复,也无需生成代理。
引言
这是一个演示如何使用多种基于 MS 的客户端,这些客户端都与同一个 WCF 服务通信。使用的是基于 REST 的 WCF 服务,因此无需生成代理,也无需处理特定于框架的 WCF 限制。
背景
我最近参与的一个项目有一个 .NET Compact Framework 和 WPF 客户端访问同一个 WCF 服务。这个项目非常出色,但其中一点我想改变的是,领域和服务的访问在两个客户端之间是重复的——因此我想出了一个概念验证,以演示如何使用 WCF 配合多个客户端,使用相同的代码库,且不复制代码域对象或代理。
使用代码
服务本身很简单,只是为了演示基本概念。它返回一个 InfoItem
对象列表,该对象只有两个属性:ID 和描述。这是接口上的方法签名和实现
[OperationContract]
List<InfoItem> GetComplexData();
public List<InfoItem> GetComplexData()
{
Console.WriteLine("GetComplexData executing");
return new List<InfoItem>
{
new InfoItem { Id = 1, Description = "Number 1" },
new InfoItem { Id = 2, Description = "Number 2" },
new InfoItem { Id = 3, Description = "Number 3" },
new InfoItem { Id = 4, Description = "Number 4" },
new InfoItem { Id = 5, Description = "Number 5" },
};
}
每个客户端都使用基于 REST 的 WCF 客户端以 XML 格式获取数据,并将其反序列化为域对象列表。用于域对象的文件与所有三个客户端都相同,尽管这三个客户端是分别编译到不同框架的。每个客户端提交的请求都类似于下面的内容。我最终希望将 REST Starter Kit 用于所有三个客户端,但我不想费力地为 CF 和 Silverlight 框架编译它。因此,目前 RESTUtil 类是一个自定义类,包含一些执行标准 HTTP 操作的方法,以从每个服务获取 XML 响应。
var infoList = RESTUtil.GetList<InfoItem>(
"https://:2890",
IService1Uri.GetComplexData
);
用于基于 REST 的契约的 URI 位于一个仅用于 URI 的静态类中,这样就可以在一个地方(在 WCF 接口上)进行更改,而无需在任何客户端上更改。实际上,这个示例并未实际使用 HTTP 状态码等内容,这些内容会让它成为一个“真正的”基于 REST 的服务。我只是为了简化演示而对请求/响应过程感兴趣。有关设置 REST 服务的详细信息的精彩文章,请参阅此 MSDN 文章:http://msdn.microsoft.com/en-us/library/dd203052.aspx。
在介绍不同的客户端之前,这里是用于在所有不同项目类型上编译相同域对象的关键技巧:每个客户端都添加到已存在的、与控制台客户端配合使用的文件中,但使用 Visual Studio “添加现有文件”对话框中的“添加为链接”功能添加。在所有平台上使用同一个文件的唯一问题是,您必须使其能够在这三个平台上进行编译。为此,我只是在不支持某些功能的客户端上添加了占位符属性,例如 DataContract
、OperationContract
等。这里是为 Compact Framework 使用的(Silverlight 客户端也有类似的变通方法)
namespace System.Runtime.Serialization
{
public class DataContractAttribute : Attribute { }
public class ServiceContractAttribute : Attribute { }
public class OperationContractAttribute : Attribute { }
public class DataMemberAttribute : Attribute
{ public int Order { get; set; } }
}
namespace System.ServiceModel.Web
{
public class WebGetAttribute : Attribute
{ public string UriTemplate { get; set; } }
}
这些属性实际上什么也不做,它们只是允许我们在 Compact Framework 和 Silverlight 框架中使用与服务和富客户端相同的接口文件,因为这些框架不具备标准 .NET 框架的所有功能。同样的技巧也用于域对象和 RESTUtil 类,因此在任何客户端中使用的唯一特定于框架的代码都在 BogusAttributes
类中,与上述类似。
控制台客户端
控制台客户端是最容易工作的,因为可以使用所有 .NET 框架。WPF、Web 和 WinForm 客户端也是如此。
var infoList = RESTUtil.GetList<InfoItem>(
"https://:2890",
IService1Uri.GetComplexData
);
foreach (var item in infoList)
{
Console.WriteLine("Id: {0}, Description: {1}", item.Id, item.Description);
}
如果您只有这些类型的客户端,那么您可以更容易地实现无代理 WCF,只需执行以下操作。也许,最终,这也能在 CF 和 Silverlight 上奏效。
var channelFactory = new ChannelFactory<IService1>(new NetTcpBinding(), endPoint);
client = channelFactory.CreateChannel();
但是,如果您不使用标准的 .NET 框架,您将需要诉诸 XML 反序列化来获取数据。为了保持一致性,我在所有客户端上都使用了这种方法。
测试项目
项目中包含一个集成测试项目,该项目启动一个 WCF 主机,并测试客户端的连接、仅获取 XML,以及最终使用业务对象。您可以使用这些来理解涉及的基本概念,并查看服务返回的原始 XML。
[TestMethod]
public void CanCallService()
{
string response = RESTUtil.GetRawResponse(endPoint + "/test/?value=47");
Assert.AreEqual(
"<string xmlns=\"http://schemas.microsoft.com/" +
"2003/10/Serialization/\">" +
"You entered: 47</string>",
response
);
}
[TestMethod]
public void CanGetComplexDataTyped()
{
var infoList = RESTUtil.GetList<InfoItem>(endPoint, IService1Uri.GetComplexData);
Assert.AreEqual(5, infoList.Count);
}
.NET Compact Framework 客户端
下面显示了用于 Compact Framework 的代码。对于 Compact Framework 来说,唯一的真正注意事项是网络功能的实现,这并不容易。为了使模拟器正常工作,我不得不在网络设置中点击一段时间,将它们都指向同一个网卡,而这个网卡必须是当前在网络上工作的网卡(即使是访问同一台机器,也不能断开连接)。此外,我不得不使用 TcpTrace 工具将端口 8080 转发到实际端口。我没有花太多时间去弄清楚原因,因为这不是文章的重点,但最终还是实现了。
infoItemBindingSource.DataSource = RESTUtil.GetList<InfoItem>(
"http://192.168.1.64:8080", IService1Uri.GetComplexData
);
Silverlight 客户端
使 Sliverlight 客户端工作起来的唯一困难是,在使用自托管 WCF 服务时,如何设置跨域策略。我找到了一篇关于如何实现这一点的文章,并在代码中包含了对该文章的引用,基本上是在服务本身上设置这两个所需文件的终结点,并手动将文件内容作为字符串返回。
由于 Silverlight 框架竭力禁用所有同步操作,并且仅提供 Begin/End 样式的异步操作,因此我以类似的方式实现了其中的一个。
RESTUtil.GetListAsync<InfoItem>(
"https://:2890",IService1Uri.GetComplexDat,
list => Dispatcher.BeginInvoke(() =>
dataGrid.ItemsSource = list
)
);
要运行任何一个客户端,首先启动 ConsoleServer 项目。当每个客户端访问服务器时,您将在控制台上看到访问的方法名称打印出来。
关注点
使用此方法的优点是,如果您想将此同一服务公开给 Android、iPhone 或其他操作系统,您可以只发布基于 REST 的 URI,并且这些平台可以实现它们自己对该服务的用法,尽管它们无法使用本文所示的技巧来重用域对象或接口。
RESTUtil
类并非旨在成为一个完整、可用于生产环境的实现。POST、方法参数和身份验证不受支持,但我认为添加它们不会太难。如果存在另一个可以在所有不同客户端类型上编译和使用的框架,我肯定会在花费过多时间尝试升级 RESTUtil
类之前使用它。如果您知道一种适用于文章中显示的所有三种客户端类型的无代理方法,请发表评论。我甚至可以想象在每个框架上使用不同的 HTTP/REST 实现,只要每个实现都能够重用相同的域/接口类,并且不需要在不同框架之间保持同步。
历史
- 2010 年 2 月 7 日:初始版本。