WCF技术的不同应用场景及其实现分析

这一篇文章,是总结一下WCF技术,以及基于这个技术发展出来的几个典型应用场景,并且我将尝试对这些不同的WCF实现的原理进行一些比较分析。

关于WCF这个技术的基本概念,如果你不是很清楚,可以参考一下有关的文档

微软开发者中心

http://msdn.microsoft.com/en-us/netframework/aa663324

我的博客中先前也有很多这方面的介绍

http://zzk.cnblogs.com/s?w=blog%3achenxizhang+wcf&p=1

言归正传,我将先概括一下WCF技术的背景、核心要素、目前的典型应用场景。然后针对这些应用场景进一步地展开一些分析和比较,以便帮助大家更好地理解,并且知道在何时应该选用哪一种场景。(即便你没有时间去看那么多资料,通过本文也将有一个提纲挈领的认识)

历史背景:

WCF,全称是Windows Communication Founcation,它作为微软新一代的通讯技术,首先正式出现在.NET Framework 3.0中,伴随着Windows Vista的发布而名噪一时。在此之前,曾经作为Winfx的一部分为人所知,其代号为indigo。

作为Foundation(基础模块),WCF是定位在微软.NET平台上实现统一的消息服务通讯机制。它很好地吸收了之前的多种分布式开发技术的优点,并且提供了统一的编程和访问的模型,逐渐成为SOA解决方案中的主流技术,受到了广泛的关注和使用。

image

核心要素:

从技术层面理解WCF,可以分为三个要素(俗称WCF的ABC)

1.Address(where):地址,是指访问服务的URI(可以是一个http地址,也可以是tcp的地址)

2.Binding(how):绑定,是指通讯所使用的协议,例如http,net.tcp,msmq等等

3.Contract(what):合约,是指通讯的规范,例如服务的合约,数据的合约等等。

从系统层面理解WCF,可以分为四个要素

1.Contract:合约,定义一套合约,通常是WCF开发的起点。这也是唯一需要在宿主和客户端之间共享的信息,通常是一些接口(interface)定义。

2.Service:服务,基于合约实现的一个具体服务。通常是一些类型(class)定义,实现了业务逻辑。

3.Host:宿主,既然服务是一个class,它自身是无法对客户端请求进行响应的。所以需要有一个宿主程序来提供持续的监听。WCF的宿主可以是任意的应用程序,非常灵活。

4.Client:客户端,任何客户端(例如Windows Forms,WPF, Silverlight,Console Application,甚至Javascript,或者java,php等等)都可以通过自己的方式来访问WCF.

应用场景:

WCF 从发布到现在的将近5年左右的时间,经过了多次增强和改进,从最开始单纯的SOAP Service的方式,发展出来其他多种应用场景,分别是

1. SOAP Services

2. WebHttp Services

3. Data Services

4. Workflow Services

5. RIA Services

下面我将针对这些应用场景一一进行讲解

【注意】我经常使用WCF技术以及一些讲座中,都会感慨WCF配置的强大。宿主和客户端都可以通过配置文件的方式定义、更改WCF服务的行为。可以这么说,在WCF中,几乎什么都可以配置。这确实很酷。所以下面的讲解,很多都是用配置文件的方式来说明问题。

1. SOAP Services

这种场景是WCF一开始就支持的,也是最完整的一个。

为什么称为SOAP Services呢?这是因为WCF服务是基于消息的通讯机制,而它的消息是被封装为一个SOAP Envelope(SOAP 信封的)

【备注】SOAP的全称是Simple Object Access Protocol,我们一般翻译为简单对象访问协议。

一个典型的SOAP Request(代表了客户端发到服务器的请求)

image

一个典型的SOAP Response

image

这种服务是以操作(Operation)为中心的,也就是说,我们可以完全控制服务的所有细节,包括定义合约,实现服务,实现宿主等等。这里面有两层意思,我们将有足够的灵活性,因为所有一切都是可以控制的;同时,我们也需要具备足够的专业知识,因为所有一切都需要你自己控制。

我们来看一个典型的WCF SOAP Service的配置文件

  <system.serviceModel>
    <services>
      <service name="WcfService2.Service1" behaviorConfiguration="WcfService2.Service1Behavior">
        <!-- Service Endpoints -->
        <endpoint address="" binding="wsHttpBinding" contract="WcfService2.IService1">
          <!-- 
              Upon deployment, the following identity element should be removed or replaced to reflect the 
              identity under which the deployed service runs.  If removed, WCF will infer an appropriate identity 
              automatically.
          -->
          <identity>
            <dns value="localhost"/>
          </identity>
        </endpoint>
        <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
      </service>
    </services>
    <behaviors>
      <serviceBehaviors>
        <behavior name="WcfService2.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>

SOAP Services是从.NET Framework 3.0一开始就提供的,在后续版本中,也有很多改进,包括对WS-*标准更好的支持,以及提供了很多默认的配置选项,简化了配置的工作。

【备注】值得一提的是,微软研发集团上海办公室这边有一个团队,直接参与了WCF的新版本的很多功能和工具的设计和开发,包括对于配置的简化设计。感谢他们的工作。

我们来看一个.NET Framework 4.0下面的WCF服务默认的配置文件

  <system.serviceModel>
    <behaviors>
      <serviceBehaviors>
        <behavior>
          <!-- 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>
    <serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
  </system.serviceModel>

通过简单地比较就可以知道,在.NET Framework 4.0中,我们并没有看到端点(EndPoint)的定义,这是怎么回事呢?难道现在不需要定义端点了吗?当然不是,区别在于,.NET Framework4的ServiceHost将自动地注册几个默认的EndPoint。

【备注】如果不是为了在开发阶段调试需要而开启元数据和调试支持,上面的配置文件,甚至可以是空的,什么都不需要定义。

关于ServiceHost以及默认的端点的信息,有兴趣可以参考

http://msdn.microsoft.com/en-us/library/system.servicemodel.servicehost.aspx

总结:采用这种方式进行开发的WCF,可以根据我们的需求,使用任意的Binding,以支持不同的客户端,并且提供在不同的场合下最好的速度,还可以实现诸如缓存,队列,事务协调等高级功能。

2. WebHttp Services

这种服务的出现,是基于一个比较热的概念:RESTFul。可以这么说,这是WCF Restful的一个具体实现。从.NET Framework 3.5开始提供。

所谓RESTFul的概念,有兴趣可以参考 http://zh.wikipedia.org/wiki/REST 以及我之前写过的一些博客文章:http://zzk.cnblogs.com/s?w=blog%3Achenxizhang%20rest

大致的意思是:

表象化状态转变(英文:Representational State Transfer,简称REST)是Roy Fielding博士在2000年他的博士论文中提出来的一种软件架构风格。

目前在三种主流的Web服务实现方案中,因为REST模式的Web服务与复杂的SOAPXML-RPC对比来讲明显的更加简洁,越来越多的web服务开始采用REST风格设计和实现。例如,Amazon.com提供接近REST风格的Web服务进行图书查找;雅虎提供的Web服务也是REST风格的。

REST 从资源的角度来观察整个网络,分布在各处的资源由URI确定,而客户端的应用通过URI来获取资源的表形。获得这些表形致使这些应用程序转变了其状态。随着不断获取资源的表形,客户端应用不断地在转变着其状态,所谓表形化的状态转变(Representational State Transfer)。

今天不是专门来探讨REST的,我们主要看看WCF是如何实现对REST支持,以及如何使用这种风格的服务。

实现WCF Restful,关键在于一个新的Binding方式,也就是WebHttpBinding。所以这种服务,我这里将其称为WebHttp Services。

WebHttp Services是在传统的SOAP Services基础上的一个增强,它仍然是基于操作(Operation)的,只不过这些Operation可以直接通过Uri访问到,而无需客户去编写一个特殊的客户端。

同时,WebHttp Services提供了两种不同的消息格式,第一种是XML,第二种是Json。这将更加有利于诸如Javascript这种客户端来访问服务。

要实现WebHttp,我们首先要添加一个引用,如下

image

然后 ,我们可以定义一个特殊的Operation

        [OperationContract]
        [WebGet]
        string HelloWorld();

【注意】这里通过WebGet这个Attribute,声明该操作是可以直接在Http访问中访问的

下面是该操作的实现

using System;
using System.ServiceModel.Activation;

namespace WcfService1
{
    // NOTE: You can use the "Rename" command on the "Refactor" menu to change the class name "Service1" in code, svc and config file together.
    [AspNetCompatibilityRequirements(RequirementsMode= AspNetCompatibilityRequirementsMode.Required)]
    public class Service1 : IService1
    {
        public string GetData(int value)
        {
            return string.Format("You entered: {0}", value);
        }

        public CompositeType GetDataUsingDataContract(CompositeType composite)
        {
            if (composite == null)
            {
                throw new ArgumentNullException("composite");
            }
            if (composite.BoolValue)
            {
                composite.StringValue += "Suffix";
            }
            return composite;
        }


        public string HelloWorld()
        {
            return "Hello,world";
        }
    }
}

【备注】该服务必须声明为AspNetCompatibility

为了使用该服务支持WebHttpBinding,我们需要修改配置文件如下(粗体部分是我们这次添加的)

<?xml version="1.0"?>
<configuration>

  <system.web>
    <compilation debug="true" targetFramework="4.0" />
  </system.web>
  <system.serviceModel>
    <services>
      <service name="WcfService1.Service1">
        <endpoint address="" behaviorConfiguration="WcfService1.Service1AspNetAjaxBehavior"
          binding="webHttpBinding" contract="WcfService1.IService1" />
      </service>
    </services>
    <behaviors>
      <endpointBehaviors>
        <behavior name="WcfService1.Service1AspNetAjaxBehavior">
          <enableWebScript />
        </behavior>
      </endpointBehaviors>
      <serviceBehaviors>
        <behavior name="">
          <serviceMetadata httpGetEnabled="true" />
          <serviceDebug includeExceptionDetailInFaults="false" />
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <serviceHostingEnvironment aspNetCompatibilityEnabled="true"
      multipleSiteBindingsEnabled="true" />
  </system.serviceModel>
 <system.webServer>
    <modules runAllManagedModulesForAllRequests="true"/>
  </system.webServer>
  
</configuration>

如果是这样的定义,那么在浏览器中我们就可以直接访问该服务及其操作

image

既然是RESTful,那么就可以直接在地址栏像下面这样访问

image

我们发现它的返回值是application/json格式的

image

具体返回的是什么内容呢?

image

也就是说,WebHttp Service默认是返回json格式的数据的,这就很容易在JAVASCRIPT中使用该服务。例如

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="default.aspx.cs" Inherits="WcfService1._default" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
    <script src="jquery-1.4.3.min.js" type="text/javascript"></script>
    <script type="text/javascript" language="javascript">
        $(function () {
            $("#btHelloworld").click(function () {
                var uri = "Service1.svc/HelloWorld";
                $.getJSON(uri, null, function (result) {
                    alert(result.d);
                });
            });

        });
    </script>
</head>
<body>
    <form id="form1" runat="server">
    <div>
    <input type="button" id="btHelloworld"  value="call webhttp Service"/>
    </div>
    </form>
</body>
</html>

在页面中测试的效果如下

image

值得注意的是,WebHttp Service除了支持Json格式之外,也支持XML格式,我们可以通过修改WebGet这个Attribute的设置来达到目的

        [OperationContract]
        [WebGet(ResponseFormat=WebMessageFormat.Xml)]
        string HelloWorld();

也可以通过配置文件的方式来设置(注意粗体部分)

<?xml version="1.0"?>
<configuration>

  <system.web>
    <compilation debug="true" targetFramework="4.0" />
  </system.web>
  <system.serviceModel>
    <services>
      <service name="WcfService1.Service1">
        <endpoint address="" behaviorConfiguration="WcfService1.Service1AspNetAjaxBehavior"
          binding="webHttpBinding" contract="WcfService1.IService1" />
      </service>
    </services>
    <behaviors>
      <endpointBehaviors>
        <behavior name="WcfService1.Service1AspNetAjaxBehavior">
          <enableWebScript />
          <webHttp defaultOutgoingResponseFormat="Xml"/>
        </behavior>
      </endpointBehaviors>
      <serviceBehaviors>
        <behavior name="">
          <serviceMetadata httpGetEnabled="true" />
          <serviceDebug includeExceptionDetailInFaults="false" />
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <serviceHostingEnvironment aspNetCompatibilityEnabled="true"
      multipleSiteBindingsEnabled="true" />
  </system.serviceModel>
 <system.webServer>
    <modules runAllManagedModulesForAllRequests="true"/>
  </system.webServer>
  
</configuration>

如果用XML格式的话,那么在浏览器测试的效果如下

image

image

总结:WebHttp Services是在SOAP Services基础上的一个改进,它不是返回SOAP Evenlope,而是根据需要返回XML或者JSON数据。这样的设计,目的是让服务更加易于使用。同时,该服务是WCF Restful的具体实现。

【备注】 由于篇幅过长,另外三种服务,我将另外单独用一篇文章来分析比较,敬请期待

3. Data Services

4. Workflow Services

5. RIA Services

我总结介绍了WCF技术的背景,核心要素,和典型场景,目的是希望让大家更好地掌握WCF技术,并且在工作中知道如何选择。

由于篇幅较长,这一篇继续写完第二部分。

应用场景:

WCF 从发布到现在的将近5年左右的时间,经过了多次增强和改进,从最开始单纯的SOAP Service的方式,发展出来其他多种应用场景,分别是

1. SOAP Services

2. WebHttp Services

3. Data Services

4. Workflow Services

5. RIA Services

本文将讨论后面3种应用场景。

3. Data Services

Data Service,顾名思义,是指数据服务。最开始的名称叫做ADO.NET Data Service,是从.NET Framework 3.5 SP1开始提供的功能。在.NET Framework 4.0中,已经改名为WCF Data Service.

image

这个服务类型是WebHttp Service的一种特殊实现,也就是说,它延续了WebHttp Service的Restful的特点。但与标准的WebHttp Service,不同的是,它具有一套完整的API,包括客户端的访问API。这样也就允许,它既支持类似于Javascript这样的脚本访问,也支持在传统客户端中进行访问。

需要注意的是,WCF Data Service既然是数据服务,它天生就是为数据访问有关。这是它最强的地方,它的出现大大简化了我们编写数据访问服务的工作。好不夸张地说,确实很酷。你可以想象一下,你有一个数据库,有N张表,你想实现对这些表的增删改查操作,如果你一个一个去编写,显然是一件非常辛苦而且没有效率的事情。

WCF Data Service支持两种数据模型,一种是LINQ to SQL, 一种是ADO.NET Entity Frmawork。下面的例子使用了LINQ to SQL. 使用的数据库是微软提供的范例数据库Northwind.

image

有了上面这个模型之后,我们就可以添加一个WCF Data Service了。这是一个继承子DataService的类型,并且通过svc这种Self Host的方式提供宿主。

using System.Data.Services;
using System.Data.Services.Common;

namespace WebApplication_WCFDataService_
{
    public class NorthwindDataService : DataService<NorthwindDataClassesDataContext>
    {
        // This method is called only once to initialize service-wide policies.
        public static void InitializeService(DataServiceConfiguration config)
        {
            // TODO: set rules to indicate which entity sets and service operations are visible, updatable, etc.
            // Examples:
            config.SetEntitySetAccessRule("*", EntitySetRights.AllRead);
            // config.SetServiceOperationAccessRule("MyServiceOperation", ServiceOperationRights.All);
            config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2;
        }
    }
}

这样就完成了所有的操作,在浏览器中可以通过REST的风格测试该服务。例如,下面的语法是查看所有Customers的

image

下面是查看某个Customer信息的

image

我们可以看到,默认情况下,WCF服务是用XML返回数据的。关于如何让它返回JSON数据,我之前有一篇文章专门探讨。请参考:http://www.cnblogs.com/chenxizhang/archive/2011/06/12/2078830.html

值得注意的是,与WebHttp Service不同的是,Data Service可以在标准客户端中进行访问。而且它的方式是有些特殊的,具体来说,客户端也是通过一个DataServiceContext的类型来访问的. 例如下面这样(这个文件是我们添加服务引用时自动生成的)

image

我们可以通过如下这样的代码进行调用

using System;
using System.Linq;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            var ctx = new DataModel.NorthwindDataClassesDataContext(
                new Uri("http://localhost.:9475/NorthwindDataService.svc/"));

            var query = from c in ctx.Customers
                        select c;

            foreach (var item in query)
            {
                Console.WriteLine(item.CompanyName);
            }
        }
    }
}

【注意】这里调用的方式,返回的数据是XML格式的,而不是SOAP Envelope的那种方式。

image

关于Data Service与WebHttp Service的相同之处,还体现在它也可以添加自定义的Operation,例如下面这篇文章介绍的那样

http://www.cnblogs.com/chenxizhang/archive/2010/02/28/1675270.html

常见的问题还有:Data Service如何做身份验证?我之前写过一篇文章介绍这个问题:http://www.cnblogs.com/chenxizhang/archive/2010/02/28/1675307.html

总结:Data Service是一种结合数据模型快速实现数据访问的方式。它依赖LINQ to SQL或者ADO.NET Entity Framework,并且提供了RESTFul的实现(它所使用的绑定就是WebHttpBinding)。它有一套专用的安全模型,与标准的WCF不一样。

4. Workflow Services

这是一个很有意思的服务。这是在.NET Framework 4.0中开始出现的,也就是随着Workflow Foundation升级到4.0之后,提供了一种全新的服务类型,简单地来说,它是可以直接与Workflow Foundation(工作流)想结合的一种服务。

image

创建好的项目中,包含了一个特殊的文件,叫xamlx。

image

虽然该文件很怪异,但它在浏览器中打开来看的效果却有标准的WCF服务没有什么区别,如下

image

不少朋友都不理解为什么需要这样一个服务,我相信正在看这篇文章的你也一定心存疑惑吧?

这是一个WCF服务,但为什么要与Workflow(工作流)扯上关系呢?

答案就是:如果你这个WCF服务,要提供的功能,希望用工作流的方式来设计和实现的话,你就可以用这个服务

换句话说,这里的重点,仍然是WCF服务,而不是Workflow。Workflow是实现的一种方式。具体来说,在这种方式中,你可以不写任何代码就能实现一些流程功能。

Workflow的讨论显然超出了本文的范畴,这里就不多展开了。

以上面这个服务为例,它是公开了一个方法叫“GetData”,该方法接受一个参数,叫“data”,是整数型的。默认情况下,这个服务会将用户传入的参数data作为响应直接发给调用方。

那么,如何理解工作流设计呢?假设我们希望为该服务添加一个简单逻辑,用户如果传入的data是偶数,则返回“偶数”,否则返回“奇数”这样的文本字符串。我们可以将这个服务稍做修改。如下图所示

image

大家请注意,实现这个功能,我们并不需要编写代码。

实际上,上面这个服务就是一段XAML的定义。这种做法自有它的好处,但这里不深入讨论了。

<WorkflowService mc:Ignorable="sap" ConfigurationName="Service1" sap:VirtualizedContainerService.HintSize="561,620" Name="Service1" mva:VisualBasic.Settings="Assembly references and imported namespaces serialized as XML namespaces" xmlns="http://schemas.microsoft.com/netfx/2009/xaml/servicemodel" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mv="clr-namespace:Microsoft.VisualBasic;assembly=System" xmlns:mva="clr-namespace:Microsoft.VisualBasic.Activities;assembly=System.Activities" xmlns:p="http://tempuri.org/" xmlns:p1="http://schemas.microsoft.com/netfx/2009/xaml/activities" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:s1="clr-namespace:System;assembly=System" xmlns:s2="clr-namespace:System;assembly=System.Xml" xmlns:s3="clr-namespace:System;assembly=System.Core" xmlns:s4="clr-namespace:System;assembly=System.ServiceModel" xmlns:sa="clr-namespace:System.Activities;assembly=System.Activities" xmlns:sad="clr-namespace:System.Activities.Debugger;assembly=System.Activities" xmlns:sap="http://schemas.microsoft.com/netfx/2009/xaml/activities/presentation" xmlns:scg="clr-namespace:System.Collections.Generic;assembly=System" xmlns:scg1="clr-namespace:System.Collections.Generic;assembly=System.ServiceModel" xmlns:scg2="clr-namespace:System.Collections.Generic;assembly=System.Core" xmlns:scg3="clr-namespace:System.Collections.Generic;assembly=mscorlib" xmlns:sd="clr-namespace:System.Data;assembly=System.Data" xmlns:sl="clr-namespace:System.Linq;assembly=System.Core" xmlns:st="clr-namespace:System.Text;assembly=mscorlib" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
  <p1:Sequence DisplayName="Sequential Service" sad:XamlDebuggerXmlReader.FileName="d:	empWCFTechonologyComparaionSolutionDeclarativeServiceLibrary1Service1.xamlx" sap:VirtualizedContainerService.HintSize="531,590" mva:VisualBasic.Settings="Assembly references and imported namespaces serialized as XML namespaces">
    <p1:Sequence.Variables>
      <p1:Variable x:TypeArguments="CorrelationHandle" Name="handle" />
      <p1:Variable x:TypeArguments="x:Int32" Name="data" />
      <p1:Variable x:TypeArguments="x:String" Name="result" />
    </p1:Sequence.Variables>
    <sap:WorkflowViewStateService.ViewState>
      <scg3:Dictionary x:TypeArguments="x:String, x:Object">
        <x:Boolean x:Key="IsExpanded">True</x:Boolean>
      </scg3:Dictionary>
    </sap:WorkflowViewStateService.ViewState>
    <Receive x:Name="__ReferenceID0" CanCreateInstance="True" DisplayName="ReceiveRequest" sap:VirtualizedContainerService.HintSize="509,90" OperationName="GetData" ServiceContractName="p:IService">
      <Receive.CorrelationInitializers>
        <RequestReplyCorrelationInitializer CorrelationHandle="[handle]" />
      </Receive.CorrelationInitializers>
      <ReceiveMessageContent>
        <p1:OutArgument x:TypeArguments="x:Int32">[data]</p1:OutArgument>
      </ReceiveMessageContent>
    </Receive>
    <p1:If Condition="[data Mod 2 = 0]" sap:VirtualizedContainerService.HintSize="509,206">
      <p1:If.Then>
        <p1:Assign sap:VirtualizedContainerService.HintSize="242,100">
          <p1:Assign.To>
            <p1:OutArgument x:TypeArguments="x:String">[result]</p1:OutArgument>
          </p1:Assign.To>
          <p1:Assign.Value>
            <p1:InArgument x:TypeArguments="x:String">偶数</p1:InArgument>
          </p1:Assign.Value>
        </p1:Assign>
      </p1:If.Then>
      <p1:If.Else>
        <p1:Assign sap:VirtualizedContainerService.HintSize="242,100">
          <p1:Assign.To>
            <p1:OutArgument x:TypeArguments="x:String">[result]</p1:OutArgument>
          </p1:Assign.To>
          <p1:Assign.Value>
            <p1:InArgument x:TypeArguments="x:String">奇数</p1:InArgument>
          </p1:Assign.Value>
        </p1:Assign>
      </p1:If.Else>
    </p1:If>
    <SendReply Request="{x:Reference __ReferenceID0}" DisplayName="SendResponse" sap:VirtualizedContainerService.HintSize="509,90">
      <SendMessageContent>
        <p1:InArgument x:TypeArguments="x:String">[result]</p1:InArgument>
      </SendMessageContent>
    </SendReply>
  </p1:Sequence>
</WorkflowService>

那么,这个服务怎么使用呢?没有什么特别的,就和正常的WCF服务一样就可以了:添加服务引用,然后调用服务。

image

需要注意的是,这个服务是使用basicHttpBinding的,而且也无法修改

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <system.serviceModel>
        <bindings>
            <basicHttpBinding>
                <binding name="BasicHttpBinding_IService" closeTimeout="00:01:00"
                    openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00"
                    allowCookies="false" bypassProxyOnLocal="false" hostNameComparisonMode="StrongWildcard"
                    maxBufferSize="65536" maxBufferPoolSize="524288" maxReceivedMessageSize="65536"
                    messageEncoding="Text" textEncoding="utf-8" transferMode="Buffered"
                    useDefaultWebProxy="true">
                    <readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
                        maxBytesPerRead="4096" maxNameTableCharCount="16384" />
                    <security mode="None">
                        <transport clientCredentialType="None" proxyCredentialType="None"
                            realm="" />
                        <message clientCredentialType="UserName" algorithmSuite="Default" />
                    </security>
                </binding>
            </basicHttpBinding>
        </bindings>
        <client>
            <endpoint address="http://localhost:14262/Service1.xamlx" binding="basicHttpBinding"
                bindingConfiguration="BasicHttpBinding_IService" contract="WorkflowService.IService"
                name="BasicHttpBinding_IService" />
        </client>
    </system.serviceModel>
</configuration>

我们看看程序调用的效果吧

using System;
using System.Linq;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {

            var proxy = new WorkflowService.ServiceClient();
            Console.WriteLine(proxy.GetData(20));//这个会返回“偶数”
            Console.WriteLine(proxy.GetData(215));//这个会返回“奇数”

            Console.Read();
        }
    }
}

image

总结:Workflow Services是一种将Workflow Foundation与WCF结合的实现,它采用了basicHttpBinding, 允许我们通过声明(Declarative)而不是代码(Code)设计一个服务。

【备注】需要注意的是,在Workflow 的设计中,也可以发起WCF 调用,但这是另外一个问题了。

image

大家有点晕了对吧,我就不再往下展开了吧。

5. RIA Services

这是本文最后一个小节,将介绍RIA Services。

RIA的意思是,Rich Internet Application。在微软平台上,Silverlight就是RIA战略中的核心产品,所以很显然,RIA Service主要就是为Silverlight服务的。这个是.NET Framework 4.0中才有的功能,并且还需要安装RIA Service Toolkit。

推荐你使用微软免费提供的Web Platform Installer来安装必要的一些组件

image

如果你对RIA Service一些基本概念不熟悉,请参考微软官方介绍 http://www.silverlight.net/getstarted/riaservices/

我们一般可以在新建Silverlight应用程序的时候,选择是否启用WCF RIA Services,如下图所示

image

启用该服务的意思是,让Silverlight程序链接到Web程序

image

为什么需要这样一个链接呢?这是因为RIA Service会有一个特殊的机制,在Silverlight应用程序编译的时候,会自动去找到所链接的Web项目,将里面的RIA Service在本地生成一个代理类。

RIA Services,具体到Web项目中来,其实是所谓的Domain Service。

image

与WCF Data Service相似的是,Domain Service也需要有一个数据模型。但不同的是,它支持ADO.NET Entity Framework之外,还支持自定义业务模型。

【注意】Domain Service目前不支持LINQ to SQL模型了。

image

创建Domain Service的时候可以选择到这个数据模型

image

 

该工具会自动生成如下代码

namespace SilverlightApplication1.Web
{
    using System.Data;
    using System.Linq;
    using System.ServiceModel.DomainServices.EntityFramework;
    using System.ServiceModel.DomainServices.Hosting;
    using System.ServiceModel.DomainServices.Server;


    // Implements application logic using the NorthwindEntities context.
    // TODO: Add your application logic to these methods or in additional methods.
    // TODO: Wire up authentication (Windows/ASP.NET Forms) and uncomment the following to disable anonymous access
    // Also consider adding roles to restrict access as appropriate.
    // [RequiresAuthentication]
    [EnableClientAccess()]
    public class NorthwindDomainService : LinqToEntitiesDomainService<NorthwindEntities>
    {

        // TODO:
        // Consider constraining the results of your query method.  If you need additional input you can
        // add parameters to this method or create additional query methods with different names.
        // To support paging you will need to add ordering to the 'Customers' query.
        [Query(IsDefault = true)]
        public IQueryable<Customer> GetCustomers()
        {
            return this.ObjectContext.Customers;
        }

        public void InsertCustomer(Customer customer)
        {
            if ((customer.EntityState != EntityState.Detached))
            {
                this.ObjectContext.ObjectStateManager.ChangeObjectState(customer, EntityState.Added);
            }
            else
            {
                this.ObjectContext.Customers.AddObject(customer);
            }
        }

        public void UpdateCustomer(Customer currentCustomer)
        {
            this.ObjectContext.Customers.AttachAsModified(currentCustomer, this.ChangeSet.GetOriginal(currentCustomer));
        }

        public void DeleteCustomer(Customer customer)
        {
            if ((customer.EntityState != EntityState.Detached))
            {
                this.ObjectContext.ObjectStateManager.ChangeObjectState(customer, EntityState.Deleted);
            }
            else
            {
                this.ObjectContext.Customers.Attach(customer);
                this.ObjectContext.Customers.DeleteObject(customer);
            }
        }
    }
}

与此同时,由于我的Silverlight 应用程序是链接到了这个Web项目的,所以如果此时编译Silverlight应用程序的话,会自动根据这个DomainService生成一个类型

image

这个类型的大致代码如下

//------------------------------------------------------------------------------
// <auto-generated>
//     This code was generated by a tool.
//     Runtime Version:4.0.30319.235
//
//     Changes to this file may cause incorrect behavior and will be lost if
//     the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------

namespace SilverlightApplication1
{
    using System;
    using System.ServiceModel.DomainServices.Client.ApplicationServices;
    
    
    /// <summary>
    /// Context for the RIA application.
    /// </summary>
    /// <remarks>
    /// This context extends the base to make application services and types available
    /// for consumption from code and xaml.
    /// </remarks>
    public sealed partial class WebContext : WebContextBase
    {
        
        #region Extensibility Method Definitions

        /// <summary>
        /// This method is invoked from the constructor once initialization is complete and
        /// can be used for further object setup.
        /// </summary>
        partial void OnCreated();

        #endregion
        
        
        /// <summary>
        /// Initializes a new instance of the WebContext class.
        /// </summary>
        public WebContext()
        {
            this.OnCreated();
        }
        
        /// <summary>
        /// Gets the context that is registered as a lifetime object with the current application.
        /// </summary>
        /// <exception cref="InvalidOperationException"> is thrown if there is no current application,
        /// no contexts have been added, or more than one context has been added.
        /// </exception>
        /// <seealso cref="System.Windows.Application.ApplicationLifetimeObjects"/>
        public new static WebContext Current
        {
            get
            {
                return ((WebContext)(WebContextBase.Current));
            }
        }
    }
}
namespace SilverlightApplication1.Web
{
    using System;
    using System.Collections.Generic;
    using System.ComponentModel.DataAnnotations;
    using System.Runtime.Serialization;
    using System.ServiceModel;
    using System.ServiceModel.DomainServices.Client;
    using System.ServiceModel.Web;
    
    
    /// <summary>
    /// The 'Customer' entity class.
    /// </summary>
    [DataContract(Namespace="http://schemas.datacontract.org/2004/07/SilverlightApplication1.Web")]
    public sealed partial class Customer : Entity
    {
        
        private string _address;
        
        private string _city;
        
        private string _companyName;
        
        private string _contactName;
        
        private string _contactTitle;
        
        private string _country;
        
        private Nullable<DateTime> _creationDate;
        
        private string _customerID;
        
        private string _fax;
        
        private Nullable<DateTime> _lastEditDate;
        
        private string _phone;
        
        private string _postalCode;
        
        private string _region;
        
        #region Extensibility Method Definitions

        /// <summary>
        /// This method is invoked from the constructor once initialization is complete and
        /// can be used for further object setup.
        /// </summary>
        partial void OnCreated();
        partial void OnAddressChanging(string value);
        partial void OnAddressChanged();
        partial void OnCityChanging(string value);
        partial void OnCityChanged();
        partial void OnCompanyNameChanging(string value);
        partial void OnCompanyNameChanged();
        partial void OnContactNameChanging(string value);
        partial void OnContactNameChanged();
        partial void OnContactTitleChanging(string value);
        partial void OnContactTitleChanged();
        partial void OnCountryChanging(string value);
        partial void OnCountryChanged();
        partial void OnCreationDateChanging(Nullable<DateTime> value);
        partial void OnCreationDateChanged();
        partial void OnCustomerIDChanging(string value);
        partial void OnCustomerIDChanged();
        partial void OnFaxChanging(string value);
        partial void OnFaxChanged();
        partial void OnLastEditDateChanging(Nullable<DateTime> value);
        partial void OnLastEditDateChanged();
        partial void OnPhoneChanging(string value);
        partial void OnPhoneChanged();
        partial void OnPostalCodeChanging(string value);
        partial void OnPostalCodeChanged();
        partial void OnRegionChanging(string value);
        partial void OnRegionChanged();

        #endregion
        
        
        /// <summary>
        /// Initializes a new instance of the <see cref="Customer"/> class.
        /// </summary>
        public Customer()
        {
            this.OnCreated();
        }
        
        /// <summary>
        /// Gets or sets the 'Address' value.
        /// </summary>
        [DataMember()]
        [StringLength(60)]
        public string Address
        {
            get
            {
                return this._address;
            }
            set
            {
                if ((this._address != value))
                {
                    this.OnAddressChanging(value);
                    this.RaiseDataMemberChanging("Address");
                    this.ValidateProperty("Address", value);
                    this._address = value;
                    this.RaiseDataMemberChanged("Address");
                    this.OnAddressChanged();
                }
            }
        }
        
        /// <summary>
        /// Gets or sets the 'City' value.
        /// </summary>
        [DataMember()]
        [StringLength(15)]
        public string City
        {
            get
            {
                return this._city;
            }
            set
            {
                if ((this._city != value))
                {
                    this.OnCityChanging(value);
                    this.RaiseDataMemberChanging("City");
                    this.ValidateProperty("City", value);
                    this._city = value;
                    this.RaiseDataMemberChanged("City");
                    this.OnCityChanged();
                }
            }
        }
        
        /// <summary>
        /// Gets or sets the 'CompanyName' value.
        /// </summary>
        [DataMember()]
        [Required()]
        [StringLength(40)]
        public string CompanyName
        {
            get
            {
                return this._companyName;
            }
            set
            {
                if ((this._companyName != value))
                {
                    this.OnCompanyNameChanging(value);
                    this.RaiseDataMemberChanging("CompanyName");
                    this.ValidateProperty("CompanyName", value);
                    this._companyName = value;
                    this.RaiseDataMemberChanged("CompanyName");
                    this.OnCompanyNameChanged();
                }
            }
        }
        
        /// <summary>
        /// Gets or sets the 'ContactName' value.
        /// </summary>
        [DataMember()]
        [StringLength(30)]
        public string ContactName
        {
            get
            {
                return this._contactName;
            }
            set
            {
                if ((this._contactName != value))
                {
                    this.OnContactNameChanging(value);
                    this.RaiseDataMemberChanging("ContactName");
                    this.ValidateProperty("ContactName", value);
                    this._contactName = value;
                    this.RaiseDataMemberChanged("ContactName");
                    this.OnContactNameChanged();
                }
            }
        }
        
        /// <summary>
        /// Gets or sets the 'ContactTitle' value.
        /// </summary>
        [DataMember()]
        [StringLength(30)]
        public string ContactTitle
        {
            get
            {
                return this._contactTitle;
            }
            set
            {
                if ((this._contactTitle != value))
                {
                    this.OnContactTitleChanging(value);
                    this.RaiseDataMemberChanging("ContactTitle");
                    this.ValidateProperty("ContactTitle", value);
                    this._contactTitle = value;
                    this.RaiseDataMemberChanged("ContactTitle");
                    this.OnContactTitleChanged();
                }
            }
        }
        
        /// <summary>
        /// Gets or sets the 'Country' value.
        /// </summary>
        [DataMember()]
        [StringLength(15)]
        public string Country
        {
            get
            {
                return this._country;
            }
            set
            {
                if ((this._country != value))
                {
                    this.OnCountryChanging(value);
                    this.RaiseDataMemberChanging("Country");
                    this.ValidateProperty("Country", value);
                    this._country = value;
                    this.RaiseDataMemberChanged("Country");
                    this.OnCountryChanged();
                }
            }
        }
        
        /// <summary>
        /// Gets or sets the 'CreationDate' value.
        /// </summary>
        [DataMember()]
        public Nullable<DateTime> CreationDate
        {
            get
            {
                return this._creationDate;
            }
            set
            {
                if ((this._creationDate != value))
                {
                    this.OnCreationDateChanging(value);
                    this.RaiseDataMemberChanging("CreationDate");
                    this.ValidateProperty("CreationDate", value);
                    this._creationDate = value;
                    this.RaiseDataMemberChanged("CreationDate");
                    this.OnCreationDateChanged();
                }
            }
        }
        
        /// <summary>
        /// Gets or sets the 'CustomerID' value.
        /// </summary>
        [DataMember()]
        [Editable(false, AllowInitialValue=true)]
        [Key()]
        [Required()]
        [RoundtripOriginal()]
        [StringLength(5)]
        public string CustomerID
        {
            get
            {
                return this._customerID;
            }
            set
            {
                if ((this._customerID != value))
                {
                    this.OnCustomerIDChanging(value);
                    this.ValidateProperty("CustomerID", value);
                    this._customerID = value;
                    this.RaisePropertyChanged("CustomerID");
                    this.OnCustomerIDChanged();
                }
            }
        }
        
        /// <summary>
        /// Gets or sets the 'Fax' value.
        /// </summary>
        [DataMember()]
        [StringLength(24)]
        public string Fax
        {
            get
            {
                return this._fax;
            }
            set
            {
                if ((this._fax != value))
                {
                    this.OnFaxChanging(value);
                    this.RaiseDataMemberChanging("Fax");
                    this.ValidateProperty("Fax", value);
                    this._fax = value;
                    this.RaiseDataMemberChanged("Fax");
                    this.OnFaxChanged();
                }
            }
        }
        
        /// <summary>
        /// Gets or sets the 'LastEditDate' value.
        /// </summary>
        [DataMember()]
        public Nullable<DateTime> LastEditDate
        {
            get
            {
                return this._lastEditDate;
            }
            set
            {
                if ((this._lastEditDate != value))
                {
                    this.OnLastEditDateChanging(value);
                    this.RaiseDataMemberChanging("LastEditDate");
                    this.ValidateProperty("LastEditDate", value);
                    this._lastEditDate = value;
                    this.RaiseDataMemberChanged("LastEditDate");
                    this.OnLastEditDateChanged();
                }
            }
        }
        
        /// <summary>
        /// Gets or sets the 'Phone' value.
        /// </summary>
        [DataMember()]
        [StringLength(24)]
        public string Phone
        {
            get
            {
                return this._phone;
            }
            set
            {
                if ((this._phone != value))
                {
                    this.OnPhoneChanging(value);
                    this.RaiseDataMemberChanging("Phone");
                    this.ValidateProperty("Phone", value);
                    this._phone = value;
                    this.RaiseDataMemberChanged("Phone");
                    this.OnPhoneChanged();
                }
            }
        }
        
        /// <summary>
        /// Gets or sets the 'PostalCode' value.
        /// </summary>
        [DataMember()]
        [StringLength(10)]
        public string PostalCode
        {
            get
            {
                return this._postalCode;
            }
            set
            {
                if ((this._postalCode != value))
                {
                    this.OnPostalCodeChanging(value);
                    this.RaiseDataMemberChanging("PostalCode");
                    this.ValidateProperty("PostalCode", value);
                    this._postalCode = value;
                    this.RaiseDataMemberChanged("PostalCode");
                    this.OnPostalCodeChanged();
                }
            }
        }
        
        /// <summary>
        /// Gets or sets the 'Region' value.
        /// </summary>
        [DataMember()]
        [StringLength(15)]
        public string Region
        {
            get
            {
                return this._region;
            }
            set
            {
                if ((this._region != value))
                {
                    this.OnRegionChanging(value);
                    this.RaiseDataMemberChanging("Region");
                    this.ValidateProperty("Region", value);
                    this._region = value;
                    this.RaiseDataMemberChanged("Region");
                    this.OnRegionChanged();
                }
            }
        }
        
        /// <summary>
        /// Computes a value from the key fields that uniquely identifies this entity instance.
        /// </summary>
        /// <returns>An object instance that uniquely identifies this entity instance.</returns>
        public override object GetIdentity()
        {
            return this._customerID;
        }
    }
    
    /// <summary>
    /// The DomainContext corresponding to the 'NorthwindDomainService' DomainService.
    /// </summary>
    public sealed partial class NorthwindDomainContext : DomainContext
    {
        
        #region Extensibility Method Definitions

        /// <summary>
        /// This method is invoked from the constructor once initialization is complete and
        /// can be used for further object setup.
        /// </summary>
        partial void OnCreated();

        #endregion
        
        
        /// <summary>
        /// Initializes a new instance of the <see cref="NorthwindDomainContext"/> class.
        /// </summary>
        public NorthwindDomainContext() : 
                this(new WebDomainClient<INorthwindDomainServiceContract>(new Uri("SilverlightApplication1-Web-NorthwindDomainService.svc", UriKind.Relative)))
        {
        }
        
        /// <summary>
        /// Initializes a new instance of the <see cref="NorthwindDomainContext"/> class with the specified service URI.
        /// </summary>
        /// <param name="serviceUri">The NorthwindDomainService service URI.</param>
        public NorthwindDomainContext(Uri serviceUri) : 
                this(new WebDomainClient<INorthwindDomainServiceContract>(serviceUri))
        {
        }
        
        /// <summary>
        /// Initializes a new instance of the <see cref="NorthwindDomainContext"/> class with the specified <paramref name="domainClient"/>.
        /// </summary>
        /// <param name="domainClient">The DomainClient instance to use for this DomainContext.</param>
        public NorthwindDomainContext(DomainClient domainClient) : 
                base(domainClient)
        {
            this.OnCreated();
        }
        
        /// <summary>
        /// Gets the set of <see cref="Customer"/> entity instances that have been loaded into this <see cref="NorthwindDomainContext"/> instance.
        /// </summary>
        public EntitySet<Customer> Customers
        {
            get
            {
                return base.EntityContainer.GetEntitySet<Customer>();
            }
        }
        
        /// <summary>
        /// Gets an EntityQuery instance that can be used to load <see cref="Customer"/> entity instances using the 'GetCustomers' query.
        /// </summary>
        /// <returns>An EntityQuery that can be loaded to retrieve <see cref="Customer"/> entity instances.</returns>
        public EntityQuery<Customer> GetCustomersQuery()
        {
            this.ValidateMethod("GetCustomersQuery", null);
            return base.CreateQuery<Customer>("GetCustomers", null, false, true);
        }
        
        /// <summary>
        /// Creates a new EntityContainer for this DomainContext's EntitySets.
        /// </summary>
        /// <returns>A new container instance.</returns>
        protected override EntityContainer CreateEntityContainer()
        {
            return new NorthwindDomainContextEntityContainer();
        }
        
        /// <summary>
        /// Service contract for the 'NorthwindDomainService' DomainService.
        /// </summary>
        [ServiceContract()]
        public interface INorthwindDomainServiceContract
        {
            
            /// <summary>
            /// Asynchronously invokes the 'GetCustomers' operation.
            /// </summary>
            /// <param name="callback">Callback to invoke on completion.</param>
            /// <param name="asyncState">Optional state object.</param>
            /// <returns>An IAsyncResult that can be used to monitor the request.</returns>
            [FaultContract(typeof(DomainServiceFault), Action="http://tempuri.org/NorthwindDomainService/GetCustomersDomainServiceFault", Name="DomainServiceFault", Namespace="DomainServices")]
            [OperationContract(AsyncPattern=true, Action="http://tempuri.org/NorthwindDomainService/GetCustomers", ReplyAction="http://tempuri.org/NorthwindDomainService/GetCustomersResponse")]
            [WebGet()]
            IAsyncResult BeginGetCustomers(AsyncCallback callback, object asyncState);
            
            /// <summary>
            /// Completes the asynchronous operation begun by 'BeginGetCustomers'.
            /// </summary>
            /// <param name="result">The IAsyncResult returned from 'BeginGetCustomers'.</param>
            /// <returns>The 'QueryResult' returned from the 'GetCustomers' operation.</returns>
            QueryResult<Customer> EndGetCustomers(IAsyncResult result);
            
            /// <summary>
            /// Asynchronously invokes the 'SubmitChanges' operation.
            /// </summary>
            /// <param name="changeSet">The change-set to submit.</param>
            /// <param name="callback">Callback to invoke on completion.</param>
            /// <param name="asyncState">Optional state object.</param>
            /// <returns>An IAsyncResult that can be used to monitor the request.</returns>
            [FaultContract(typeof(DomainServiceFault), Action="http://tempuri.org/NorthwindDomainService/SubmitChangesDomainServiceFault", Name="DomainServiceFault", Namespace="DomainServices")]
            [OperationContract(AsyncPattern=true, Action="http://tempuri.org/NorthwindDomainService/SubmitChanges", ReplyAction="http://tempuri.org/NorthwindDomainService/SubmitChangesResponse")]
            IAsyncResult BeginSubmitChanges(IEnumerable<ChangeSetEntry> changeSet, AsyncCallback callback, object asyncState);
            
            /// <summary>
            /// Completes the asynchronous operation begun by 'BeginSubmitChanges'.
            /// </summary>
            /// <param name="result">The IAsyncResult returned from 'BeginSubmitChanges'.</param>
            /// <returns>The collection of change-set entry elements returned from 'SubmitChanges'.</returns>
            IEnumerable<ChangeSetEntry> EndSubmitChanges(IAsyncResult result);
        }
        
        internal sealed class NorthwindDomainContextEntityContainer : EntityContainer
        {
            
            public NorthwindDomainContextEntityContainer()
            {
                this.CreateEntitySet<Customer>(EntitySetOperations.All);
            }
        }
    }
}

我们看到,其实在客户端是有一个WebContext的类型的。下面演示一下,我们具体在Silverlight里面如何使用该服务吧

MainPage.xaml

<UserControl x:Class="SilverlightApplication1.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    d:DesignHeight="300" d:DesignWidth="400" xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk">

    <Grid x:Name="LayoutRoot" Background="White">
        <sdk:DataGrid AutoGenerateColumns="true" Margin="20,26,23,12" Name="dataGrid1" />
    </Grid>
</UserControl>

MainPage.xaml.cs

using System.Windows;
using System.Windows.Controls;
using SilverlightApplication1.Web;

namespace SilverlightApplication1
{
    public partial class MainPage : UserControl
    {
        public MainPage()
        {
            InitializeComponent();
            Loaded += new RoutedEventHandler(MainPage_Loaded);
        }

        void MainPage_Loaded(object sender, RoutedEventArgs e)
        {
            var ctx = new NorthwindDomainContext();
            var op = ctx.Load<Customer>(ctx.GetCustomersQuery());
            dataGrid1.ItemsSource = op.Entities;
        }
    }
}

【备注】我们都知道Silverlight中访问数据都是异步的(一般编写代码都比较繁琐),RIA Service对此做了优化,但实际上上面这个Load方法也是一个异步方法。

image

如果我们需要对数据进行过滤,服务器不需要做任何修改。只要在Silverlight中用下面这样的语法就可以了

using System.Windows;
using System.Windows.Controls;
using SilverlightApplication1.Web;
using System.Linq;
using System.ServiceModel.DomainServices.Client;

namespace SilverlightApplication1
{
    public partial class MainPage : UserControl
    {
        public MainPage()
        {
            InitializeComponent();
            Loaded += new RoutedEventHandler(MainPage_Loaded);
        }

        void MainPage_Loaded(object sender, RoutedEventArgs e)
        {
            var ctx = new NorthwindDomainContext();
            var query = from c in ctx.GetCustomersQuery()
                        where c.Country == "USA"
                        select c;

                        
            var op = ctx.Load<Customer>(query);
            dataGrid1.ItemsSource = op.Entities;
        }
    }
}

image

这是多么方便啊。所以,我有时候也喜欢将RIA解读为:Rapid Internet Application. 它大大地提高了Silverlight应用程序开发的生产力。

那么,RIA Service后台到底是如何实现的呢?与前面几种都不同的是,它是通过一个特殊的HttpModule实现的

  <configSections>
    <sectionGroup name="system.serviceModel">
      <section name="domainServices" type="System.ServiceModel.DomainServices.Hosting.DomainServicesSection, System.ServiceModel.DomainServices.Hosting, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" allowDefinition="MachineToApplication" requirePermission="false" />
    </sectionGroup>
  </configSections>
  <system.webServer>
    <modules runAllManagedModulesForAllRequests="true">
      <add name="DomainServiceModule" preCondition="managedHandler"
        type="System.ServiceModel.DomainServices.Hosting.DomainServiceHttpModule, System.ServiceModel.DomainServices.Hosting, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
    </modules>
    <validation validateIntegratedModeConfiguration="false" />
  </system.webServer>
  <system.web>
    <httpModules>
      <add name="DomainServiceModule" type="System.ServiceModel.DomainServices.Hosting.DomainServiceHttpModule, System.ServiceModel.DomainServices.Hosting, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
    </httpModules>
    <compilation debug="true" targetFramework="4.0">
      <assemblies>
        <add assembly="System.Data.Entity, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
      </assemblies>
    </compilation>
  </system.web>

【备注】RIA Service并没有显式的SVC文件,但会有一个隐含的svc路径可以访问。(请参考下图)

那么,RIA Service是使用什么样的Binding呢?根据资料,它应该支持下面三种Binding。默认是第三种,它用二进制的方式发送数据,目的主要是提高传输效率。

  1. For WebHttpBinding: “baseAddress" (REST with JSON Endpoint)
  2. For BasicHttpBinding: “baseAddress” + “/soap” (SOAP with XML Endpoint)
  3. For BinaryHttpBinding: “baseAddress” + “/binary” (SOAP with Binary Endpoint)

image

那么,是否可以让RIA Service支持标准的SOAP endpoint呢?

这个问题,之前有一个专门的文章介绍,请参考这里:http://www.cnblogs.com/chenxizhang/archive/2011/06/14/2080887.html

总结:RIA Service是一种主要用于Silverlight应用程序的服务。它可以结合ADO.NET Entity Framework数据模型,或者自定义数据模型,极大地方便了Silverlight应用程序进行数据访问的设计。它默认是用二进制的binding,但也可以通过一些配置添加soap endpoint.

最后,本文总结分析了五种主要的WCF服务类型,以及他们的一些内在设计原理,下面这个表格可以做一个综合比较

image

转自:http://www.cnblogs.com/chenxizhang

原文地址:https://www.cnblogs.com/wayne-ivan/p/3830163.html