在Silverlight应用程序中与服务器端进行交互时,微软推荐的方法是使用WCF RIA Services。然而,这种方法在与实体框架(Entity Framework)装饰的实体一起使用时存在限制。引入了一些额外的代码属性以支持本地化,这些属性引用的类型并非实体本身,因此代码生成工具(由于WCF RIA Services链接的存在而被调用)忽略了它们,导致生成的代码无法编译。此外,对于基于DMZ的部署场景,支持也不佳,这迫使寻找其他解决方案。与其与工具“斗争”,不如选择众所周知的路径,直接使用普通的WCF服务。这个决定反过来又引入了一些摩擦,将在本系列文章中消除这些摩擦。
假设有以下服务定义:
public class CalculatorService
{
public Number Add(Number first, Number second)
{
return new Number { Value = first.Value + second.Value };
}
}
public class Number
{
public int Value { get; set; }
}
在Silverlight中使用它非常简单——只需在Visual Studio中使用“添加服务引用”向导,它将为生成所有必需的管道代码。不会列出使用这种简单方法可能遇到的每一个问题(互联网上已经有很多讨论);只是最值得注意的(至少对来说)是:生成的代理难以测试(不容易模拟),增加了结果XAP文件的大小,每次更改都需要刷新引用(乏味),不支持TDD。此外,这种方法仍然没有解决自定义属性的问题,因为为客户端生成的类与服务器端的类不同。想要的是实际上在两边共享相同的类定义。
为了重用(共享)类定义,需要引入一个服务接口,并将该接口(以及所有相关类)放在一个单独的程序集中,该程序集应该对客户端和服务器端代码都可见。然而,对于Silverlight来说并非如此。在Silverlight中,不能直接引用CLR程序集。因此,需要创建一个单独的Silverlight类库,并通过链接源文件包含类定义。
public interface ICalculatorService
{
Number Add(Number first, Number second);
}
public class CalculatorService : ICalculatorService
{
public Number Add(Number first, Number second)
{
return new Number { Value = first.Value + second.Value };
}
}
现在如果重新生成代理(在VS中更新服务引用),代码生成工具将能够重用之前链接的类定义。
第一次尝试:
var factory = new ChannelFactory<ICalculatorService>(new BasicHttpBinding(), new EndpointAddress("http://localhost/SampleSilverlightWCF"));
ICalculatorService service = factory.CreateChannel();
这是第一次尝试,一旦运行,就得到了以下结果:
惊喜,Silverlight中的WCF运行时强制只能使用异步接口进行服务调用。不能直接在Silverlight中使用同步服务接口,要么需要以异步模式实现服务操作,要么诉诸于服务代理生成(这将为服务生成一个异步接口)。这看起来像是一个阻碍。
知道应该有一个解决方案。不想以异步方式实现服务操作,也不想使用代码生成。停下来,为什么生成服务代理可以解决这个问题而不强迫以异步方式实现服务操作呢?!好吧,如果查看生成的代码,会看到它并没有重用同步服务接口;相反,它创建了它的异步副本。然后使用这个接口以异步方式与同步服务进行通信。很酷的技巧!
public interface ICalculatorService
{
Number Add(Number first, Number second);
}
public interface ICalculatorServiceAsync
{
IAsyncResult BeginAdd(Number first, Number second, AsyncCallback callback, object state);
void EndAdd(IAsyncResult result);
}
现在,为了能够成功地与服务进行交互,需要在创建WCF通道时使用异步接口:
ICalculatorServiceAsync service;
private void UserControl_Loaded(object sender, System.Windows.RoutedEventArgs e)
{
var factory = new ChannelFactory<ICalculatorServiceAsync>(new BasicHttpBinding(), new EndpointAddress("http://localhost/SampleSilverlightWCF/Services/CalculatorService.svc"));
service = factory.CreateChannel();
service.BeginAdd(new Number { Value = 2 }, new Number { Value = 2 }, OnAddCompleted, null);
}
void OnAddCompleted(IAsyncResult ar)
{
Number result = service.EndAdd(ar);
Debug.Assert(result.Value == 4);
}
效果非常好!
不需要使用静态代码生成服务代理,也不需要牺牲WCF服务的接口和实现,以便在Silverlight中使用它。需要的是: