C#入门经典2008(第4版)

35.3  WCF编程

前面介绍了基础知识,下面开始编写一些代码。本节首先看一个在Web服务器上存储的简单WCF服务和一个控制台应用程序。介绍了所创建的代码结构后,学习WCF服务和客户应用程序的基本结构。之后详细探讨一些重要主题:

●       定义WCF服务合同

●       自存储的WCF服务

试试看:一个简单的WCF服务和客户程序

(1) 在目录C:BegVCSharp\Chapter35下创建一个新的WCF服务应用程序项目Ch35Ex01

(2) 在解决方案中添加一个控制台应用程序Ch35Ex01Client。

(3) 在Build菜单上单击Build Solution选项。

(4) 在Solution Explorer中右击Ch35Ex01Client,选择Add Service Reference选项。

(5) 在Add Service Reference对话框中,单击Discover。

(6) 开始开发Web服务器,加载WCF服务的信息后,展开该引用,查看其细节,如图35-2所示(读者的端口号可能与本图中的不同)。

图  35-2

(7) 单击OK按钮,添加服务引用。

(8) 在Ch35Ex01Client应用程序中修改Pragram.cs中的代码,如下所示:

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using Ch35Ex01Client.ServiceReference1;

namespace Ch35Ex01Client

{

class Program

{

static void Main(string[] args)

{

string numericInput = null;

int intParam;

do

{

Console.WriteLine(

"Enter an integer and press enter to call the WCF service.");

numericInput = Console.ReadLine();

}

while (!int.TryParse(numericInput, out intParam));

Service1Client client = new Service1Client();

Console.WriteLine(client.GetData(intParam));

Console.WriteLine("Press an key to exit.");

Console.ReadKey();

}

}

}

(9) 在Solution Explorer中右击解决方案,选择Set StartUp Projects选项。

(10) 把两个项目都选择为启动项目,如图35-3所示,单击OK按钮。

图  35-3

(11) 右击Ch35Ex01中的Service1.svc,单击Set as StartUp Page。

(12) 运行应用程序。出现提示后,单击OK按钮激活Web.config中的调试功能。在控制台应用程序窗口中输入一个数字,按下回车键。结果如图35-4所示。

(13) 查看窗口中的信息,如图35-5所示。

图  35-4

图  35-5

(14) 单击Web页面顶部的链接,查看服务的WSDL。现在还不需要知道WSDL文件中的内容。

示例的说明

这个示例中创建了一个存储在Web服务器上的简 单Web服务和控制台客户程序。我们为WCF服务项目使用了默认的VS模板,这说明不必添加任何代码,而使用这个默认模板中定义的一个操作 GetData()。对于这个示例,使用什么操作并不重要,而应关注代码的结构及其工作方式。

首先看看服务项目Ch35Ex01,它包含:

●       Service1.svc文件,它定义了服务的主机。

●       类定义CompositeType,它定义了服务使用的数据合同。

●       接口定义IService1,它定义了服务合同和两个操作合同。

●       类定义Service1,它执行IService1接口,定义了服务的功能。

●       配置段<system.serviceModel>(在Web.config中),它配置了服务。

Service1.svc文件包含如下代码(要查看这行代码,应在Solution Explorer中右击该文件,再单击View Markup:

< %@ ServiceHost Language="C#" Debug="true" Service="Ch35Ex01.Service1"

CodeBehind="Service1.svc.cs"%>

这是一个ServiceHost指令,用于告诉 Web服务器(本例是Web开发服务器,尽管这也应用于IIS)把什么服务存储在这个地址上。定义服务的类在Service属性中声明,定义这个类的代码 文件在CodeBehind属性中声明。这个指令是必须的,以获得Web服务器的主机功能,如前面几节所述。

显然,没有存储在Web服务器上的WCF服务不需要这个文件。本章后面将学习自存储的WCF服务。

接着在IService1.cs文件中定义数据合同CompositeType。从代码中可以看出,数据合同只是一个类定义,在类定义中包含了DataContract属性,在类成员上包含了DataMember属性:

[DataContract]

public class CompositeType

{

bool boolValue = true;

string stringValue = "Hello";

[DataMember]

public bool BoolValue

{

get { return boolValue; }

set { boolValue = value; }

}

[DataMember]

public string StringValue

{

get { return stringValue; }

set { stringValue = value; }

}

}

这个数 据合同通过元数据提供给客户应用程序(查看示例中的WSDL文件,就会看到这些元数据)。这允许客户应用程序定义一个类型,该类型可以序列化到窗体上,该 窗体又可以由服务解序到CompositeType对象上。客户程序不需要知道这个类型的定义,实际上,客户程序使用的类可以有不同的执行代码。定义数据 合同的这种方式虽简单但非常强大,允许在WCF服务及其客户程序之间交换复杂的数据结构。

IService1.cs 文件还包含服务合同,该服务合同定义为带有[ServiceContract]属性的接口。这个接口也在服务的元数据中进行了完整的描述,并可以在客户应 用程序中重建。接口成员构建了服务的操作,每个操作都应用OperationContract属性创建一个操作合同。示例代码包含两个操作,每个操作都使 用了前面的数据合同:

[ServiceContract]

public interface IService1

{

[OperationContract]

string GetData(int value);

[OperationContract]

CompositeType GetDataUsingDataContract(CompositeType composite);

}

前面介绍的4个合同定义属性都可以用特性进一步配置,如下一节所述。实现服务的代码与其他类定义类似:

public class Service1 : IService1

{

public string GetData(int value)

{

return string.Format("You entered: {0}", value);

}

public CompositeType GetDataUsingDataContract(CompositeType composite)

{

if (composite.BoolValue)

{

composite.StringValue += "Suffix";

}

return composite;

}

}

注意这个类定义不需要继承自特定的类型,也不需要任何特定的属性,只需实现定义了服务合同的接口。实际上,可以在这个类及其成员中添加属性,以指定行为,但这些都不是强制的。

把服务的实现代码(类)和服务合同(接口)分开是很好的。客户程序不需要知道类的任何信息,类包含的功能可能远远超过了服务实现的功能。一个类甚至可以实现多个服务合同。

最后看看Web.config文件中的配置。在 配置文件中,WCF服务的配置是从.NET远程技术中提取出来的一个特性,可以处理所有类型的WCF服务(非自存储的服务和自存储的服务)和WCF服务的 客户程序(稍后介绍)。这个配置的语法允许把任何配置应用于服务,甚至可以扩展其语法。

WCF配置代码包含在Web.config或app.config文件的配置段<system.serviceModel>中。在这个示例的Web.config文件中,配置段包含两个子段:

●       <services>:定义项目中的服务。每个服务都在一个<services>子段中定义。

●       <behaviors>:定义<services>段中各个元素使用的行为。在<behaviors>子段中定义的行为可以在多个其他元素中重用。

这个示例只有一个服务。在配置代码中,给服务指定了一个名称,并关联了一个在<behaviors>段中定义的指定行为:

<configuration>

...

<system.serviceModel>

<services>

<service name="Ch35Ex01.Service1"

behaviorConfiguration="Ch35Ex01.Service1Behavior">

<service>元素包含两个子元素<endpoint>,每个子元素都定义了服务的一个端点。实际上,这些端点是服务的基端点。操作的端点可以从这些端点中推断出。

端点地址在address属性中定义。在Web 服务器存储的服务中,地址相对于服务的.svc文件。端点绑定在binding属性中定义,绑定的服务合同用接口名指定。第一个端点是服务的主端点,所以 使用IService1接口作为其服务合同,它还使用默认地址和WSHttpBinding绑定类型:

<endpoint  address="  "binding="wsHttpBinding"contract="Ch35Ex01.IService1">

<identity>

<dns value="localhost"/>

</identity>

</endpoint>

在元素<endpoint>中可以有各种元素,如这里使用的<identity>元素,它指定端点的基服务器地址。这是一个正在开发的服务,所以使用localhost。

示例服务在mex地址上包含第二个端点,mex 是元数据交换(metadata exchange)的缩写,它允许客户程序获得WCF服务的描述。WCF服务与Web服务不同,不默认提供服务描述。添加一个使用 IMetadataExchange合同的端点,就可以获得服务描述。服务描述端点根据所使用的协议,使用mexHttpBinding、 mexHttpsBinding、mexNamedPipeBinding或mexTcpBinding中的一个绑定。

<endpoint address="mex" binding="mexHttpBinding"

contract="IMetadataExchange"/>

</service>

</services>

在这个例子中,可以得到WSDL描述。这需要把?wsdl添加到服务地址的后面,实际上这是通过一个行为得到的,不是通过元数据交换端点得到的。这个行为应用于服务,其定义如下:

<behaviors>

<serviceBehaviors>

<behavior name="Ch35Ex01.Service1Behavior">

<serviceMetadata httpGetEnabled="true"/>

这个行为还为传输到客户机上的错误提供了异常信息,在开发时常常允许这么做:

<serviceDebug includeExceptionDetailInFaults="false"/>

</behavior>

</serviceBehaviors>

</behaviors>

</system.serviceModel>

</configuration>

这就完成了服务器的定义。

在客户应用程序中,我们使用Add Service Reference工具添加对服务的引用,使用服务的元数据(即服务的WSDL)构建代理类。这不是访问WCF服务的唯一方式,但它是最简单的方式。另一 个常见方式是在一个独立的程序集中为WCF服务定义合同,由主机项目和客户项目引用。接着客户程序直接使用这些合同生成代理,而不是通过元数据生成代理。

也可以浏览Add Service Reference工具生成的代码(显示项目中的所有文件,包括隐藏的文件),但目前最好不要浏览代码,因为有许多容易混淆的代码。

这里要注意,工具创建了访问服务所需的所有类,包括服务的代理类和从数据合同中生成的客户端类(CompositeType),服务的代理类包含服务的所有操作方法(Service1Client)。

该工具还为项目添加了一个配置文件app.config,这个配置定义了两个内容:

●       服务端点的绑定信息

●       端点的地址和合同

绑定信息从服务描述中提取,在客户程序中,每个可配置的选项都被复制到配置文件中:

<configuration>

<system.serviceModel>

<bindings>

<wsHttpBinding>

<binding name="WSHttpBinding_IService1" closeTimeout="00:01:00"

openTimeout="00:01:00"receiveTimeout="00:10:00"sendTimeout=00:01:00"

bypassProxyOnLocal="false" transactionFlow="false"

hostNameComparisonMode="StrongWildcard"maxBufferPoolSize="524288"

maxReceivedMessageSize="65536" messageEncoding="Text"

textEncoding="utf-8" useDefaultWebProxy="true"allowCookies="false">

<readerQuotas maxDepth="32" maxStringContentLength="8192"

maxArrayLength="16384" maxBytesPerRead="4096"

maxNameTableCharCount="16384"/>

<reliableSession ordered="true" inactivityTimeout="00:10:00"

enabled="false"/>

<security mode="Message">

<transport clientCredentialType="Windows"proxyCredentialType="None"

realm=" "/>

<message clientCredentialType="Windows"

negotiateServiceCredential="true" algorithmSuite="Default"

establishSecurityContext="true"/>

</security>

</binding>

</wsHttpBinding>

</bindings>

这个绑定、服务的基地址(这是Web服务器存储的服务的.svc文件地址)和合同的客户端版本IService1在端点配置中使用:

<client>

<endpoint address="http://localhost:51173/Service1.svc"

binding="wsHttpBinding"

bindingConfiguration="WSHttpBinding_IService1"

contract="ServiceReference1.IService1" name="WSHttpBinding_IService1">

<identity>

<dns value="localhost"/>

</identity>

</endpoint>

</client>

</system.serviceModel>

</configuration>

Add Service Reference工具是非常全面的。实际上,大多数信息都不是必要的,因为我们使用的是默认绑定WSHttpBinding。可以用下面的代码替代这个配置文件:

<configuration>

<system.serviceModel>

<client>

<endpoint address="http://localhost:51173/Service1.svc"

binding="wsHttpBinding" contract="ServiceReference1.IService1"

name="WSHttpBinding_IService1">

<identity>

<dns value="localhost"/>

</identity>

</endpoint>

</client>

</system.serviceModel>

</configuration>

这段代码删除了<endpoint>元素的bindingConfiguration属性,这表示客户程序将使用默认的绑定配置。

但是为了学习WCF服务,掌握工具的全面性是非常重要的。它会显示包含在WSHttpBinding默认绑定中的所有设置。本章不深入探讨WCF服务配置,但介绍了其中的一些配置,如超时设置,这些配置的命名很简单,很容易理解。

这个示例介绍了许多基础知识,下面总结一下前面的内容:

●       WCF定义

服务由服务合同接口定义,其中包括操作合同成员

服务在实现了服务合同接口的类中实现

数据合同只是使用数据合同属性的类型定义

●       WCF服务配置

可以使用配置文件(Web.config或app.config)来配置WCF服务

●       WCF Web服务器主机:

Web服务器主机把.svc文件用作服务基地址

●       WCF客户机配置:

可以使用配置文件(web.config或app.config)来配置WCF服务客户机

下面详细介绍合同。

35.3.1  定义WCF服务合同

前面的示例说明了WCF体系结构如何便于给WCF服务定义合同,包括类、接口和属性。本节将深入介绍这种技术。

1. 数据合同

要给服务定义数据合同,需要把DataContractAttribute属性应用于类定义。这个属性在名称空间System.Runtime.Serialization中。可以使用表35-2中所示的属性配置它。

表  35-2

属   性

用    法

Name

用不同于类定义的名称来命名数据合同。这个名称在SOAP消息和服务元数据定义的客户端数据对象上使用

Namespace

指定数据合同在SOAP消息中使用的名称空间

当需要与已有的SOAP消息格式交互操作时(类似于其他合同的对应属性),需要使用这两个属性,否则就不需要它们。

数据合同中的每个类成员都必须使用DataContractAttribute属性,它在名称空间System. Runtime.Serialization中。这个属性具有表35-3中所示的特性。

表  35-3

特   性

用   法

Name

指定序列化时数据成员的名称(默认为成员名称)

IsRequired

指定成员是否必须显示在SOAP消息中

 (续表)   

特   性

用   法

Order

int值,指定序列化或解序成员的顺序,如果一个成员必须在另一个成员之前出现,这个顺序就是必须的。Order较低的成员先出现

EmitDefaultValue

把它设置为false,如果成员的值是默认值,就禁止该成员包含在SOAP消息中

2. 服务合同

把System.ServieceModel.ServieceContractAttribute属性应用于接口定义,就定义了服务合同。表35-4中所示的属性可用于定制服务合同。

表  35-4

属   性

用   法

Name

按照WSDL中<portType>元素中的定义,指定服务合同的名称

Namespace

定义WSDL中<portType>元素使用的服务合同的命称空间

ConfigurationName

在配置文件中使用的服务合同名称

HasProtectionLevel

指定服务使用的消息是否有明确定义的保护级别。保护级别允许签名消息,或者签名和加密消息

ProtectionLevel

保护级别,用于保护消息

SessionMode

确定是否为消息启用会话。如果启用会话,就可以确保关联上发送给服务的不同端点的消息,即它们使用同一个服务实例,因此可以共享状态

CallbackContract

对于双向消息传输,客户机提供了合同和服务。这是因为,如前所述,双向通信中的客户机也用作服务器。这个属性允许指定客户机使用的合同

3. 操作合同

在定义服务合同的接口中,应用System.ServieceModel.OperationContractAttribute属性,就可以把成员定义为操作。这个属性具有表35-5中所示的特性。

表  35-5

属   性

说   明

Name

指定服务操作的名称。默认为成员名称

IsOneWay

指定操作是否返回一个响应。如果把它设置为true,则客户机不等待操作完成,就会继续执行

AsyncPattern

设置为true,操作就会执行为两个方法:Begin<methodName>和End<method Name>,这两个方法可用于异步调用操作

HasProtectionLevel

参见表35-4

ProtectionLevel

参见表35-4

IsInitiating

如果使用会话,这个属性就确定调用这个操作是否可以启动新会话

(续表)   

属   性

说   明

IsTerminating

如果使用会话,这个属性就确定调用这个操作是否会中断当前会话

Action

如果使用寻址功能(WCF服务的一个高级功能),操作就有一个关联的动作名称,通过这个属性可以指定该名称

ReplayAction

同上,但为操作的响应指定动作名称

4. 消息合同

前面的示例中没有使用消息合同规范。如果使用消 息合同,就应定义一个表示消息的类,再给类应用MessageContractAttribute属性。接着给这个类的成员应用Message Body MemberAttribute、MessageHeaderAttribute或MessageHeaderArrayAttribute属性。所有这 些属性都在System. ServieceModel名称空间中。如果要高度控制WCF服务使用的SOAP消息,就不要使用消息合同,所以这里不详细讨论它。

5. 误合同

如果客户应用程序可以使用特定的异常类型,如定制异常,就可以给可能生成该异常的操作应用System.ServieceModel.FaultContractAttribute属性。在最初使用WCF时不希望这么做。

试试看:WCF合同

(1) 创建一个新WCF服务应用程序项目Ch35Ex02,将其保存在C: \BegVCSharp \Chapter35目录下。

(2) 给解决方案添加一个类库项目Ch35Ex01Contracts,删除Class1.cs文件。

(3) 在Ch35Ex01Contracts项目中添加对System.Runtime.Serialization和System.Serviece Model.dll程序集的引用。

(4) 在Ch35Ex01Contracts项目中添加Person类,修改Person.cs中的代码,如下所示:

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Runtime.Serialization;

namespace Ch35Ex02Contracts

{

[DataContract]

public class Person

{

[DataMember]

public string Name { get; set; }

[DataMember]

public int Mark { get; set; }

}

}

(5) 在Ch35Ex01Contracts项目中添加IAwardService类,修改IAwardService.cs中的代码,如下所示:

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.ServiceModel;

namespace Ch35Ex02Contracts

{

[ServiceContract(SessionMode=SessionMode.Required)]

public interface IAwardService

{

[OperationContract(IsOneWay=true,IsInitiating=true)]

void SetPassMark(int passMark);

[OperationContract]

Person[] GetAwardedPeople(Person[] peopleToTest);

}

}

(6) 对于Ch35Ex01项目,添加对Ch35Ex01Contracts的引用。

(7) 删除Ch35Ex01项目中的IService1.cs和Service1.svc。

(8) 在Ch35Ex01中添加一个新的WCF服务。

(9) 删除Ch35Ex01项目中的IAwardService.cs文件。

(10) 修改AwardService.svc.cs文件中的代码,如下所示:

using System;

using System.Collections.Generic;

using System.Linq;

using System.Runtime.Serialization;

using System.ServiceModel;

using System.Text;

using Ch35Ex02Contracts;

namespace Ch35Ex02

{

public class AwardService : IAwardService

{

private int passMark;

public void SetPassMark(int passMark)

{

this.passMark = passMark;

}

public Person[] GetAwardedPeople(Person[] peopleToTest)

{

List < Person > result = new List < Person > ();

foreach (Person person in peopleToTest)

{

if (person.Mark > passMark)

{

result.Add(person);

}

}

return result.ToArray();

}

}

}

(11) 修改Web.config中的服务配置段,如下所示:

<system.serviceModel>

<services>

<service name="Ch35Ex02.AwardService">

<endpoint address=" " binding="wsHttpBinding"

contract="Ch35Ex02Contracts.IAwardService"/>

</service>

</services>

</system.serviceModel>

(12) 将Ch35Ex02的启动页面设置为AwardService.svc。

(13) 在调试模式下运行Ch35Ex02项目,记下浏览器中使用的URL(包括端口号,后面需要使用它)。

(14) 停止调试,在解决方案中添加一个新的控制台项目Ch35Ex02Client。

(15) 在Ch35Ex01Client项目中添加对System.ServieceModel.dll程序集和Ch35Ex01 Contracts的引用。

(16) 在Ch35Ex01Client项目中修改Program.cs中的代码,如下所示:

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.ServiceModel;

using Ch35Ex02Contracts;

namespace Ch35E02Client

{

class Program

{

static void Main(string[] args)

{

Person[] people = new Person[]

{

new Person { Mark = 46, Name="Jim"},

new Person { Mark = 73, Name="Mike"},

new Person { Mark = 92, Name="Stefan"},

new Person { Mark = 84, Name="George"},

new Person { Mark = 24, Name="Arthur"},

new Person { Mark = 58, Name="Nigel"}

};

Console.WriteLine("People: ");

OutputPeople(people);

 

IAwardService client = ChannelFactory < IAwardService > .CreateChannel(

new WSHttpBinding(),

new EndpointAddress("http://localhost:51425/AwardService.svc"));

client.SetPassMark(70);

Person[] awardedPeople = client.GetAwardedPeople(people);

Console.WriteLine();

Console.WriteLine("Awarded people: ");

OutputPeople(awardedPeople);

Console.ReadKey();

}

static void OutputPeople(Person[] people)

{

foreach (Person person in people)

{

Console.WriteLine("{0}, mark: {1}", person.Name, person.Mark);

}

}

}

}

(17) 运行应用程序,结果如图35-6所示。

图  35-6

示例的说明

这个示例在类库项目中创建了一系列合同,在WCF服务和客户程序中使用了这个类库。与前面的示例一样,这个服务也存储在Web服务器上。这个服务的配置也被减少到最低程度。

在这个示例中,主要区别是客户程序不需要元数据,因为客户程序可以访问合同程序集。客户程序不是从元数据中生成一个代理类,而是通过另一种方法获得服务合同接口的引用。这个示例中另一个值得注意的地方是使用会话维护服务中的状态。

这个示例使用的数据合同是一个简单的类Person,它有一个string属性Name和一个int属性Mark。使用的DataContractAttribute属性和DataMemberAttribute属性没有进行定制,也不需要给这个合同重复迭代代码。

定义服务合同时,给IAwardService接口应用ServiceContractAttribute属性。这个属性的SessionMode特性设置为SessionMode.Required,因为这个服务需要状态:

[ServiceContract(SessionMode=SessionMode.Required)]

public interface IAwardService

{

第一个操作合同SetPassMark()设置状态,因此其OperationContractAttribute属性的IsInitiating特性设置为true。这个操作不返回任何值,所以将IsOneWay设置为true,把操作定义为单向操作:

[OperationContract(IsOneWay=true,IsInitiating=true)]

void SetPassMark(int passMark);

另一个操作合同GetAwardedPeople()不需要任何定制,使用前面定义的数据合同:

[OperationContract]

Person[] GetAwardedPeople(Person[] peopleToTest);

}

这两个类型Person和 IAwardService都可以用于服务和客户程序。服务在AwardService类型中实现了IAwardService合同,它不包含任何可标记 的代码。这个类与前面的服务类的唯一区别是,这个类是有状态的。这是允许的,因为定义了一个会话,来关联来自客户程序的消息。

客户程序比较有趣,主要是因为下面这行代码:

IAwardService client = ChannelFactory < IAwardService > .CreateChannel(

new WSHttpBinding(),

new EndpointAddress("http://localhost:51425/AwardService.svc"));

客户程序没有用app.config文件配置来 与服务的通信,也没有从元数据中定义代理类,来与服务通信。而是通过ChannelFactory<T>.CreateChannel()方 法创建代理类。这个方法创建了一个执行IAwardService客户程序的代理类,但在后台生成的类与服务通信,就像前面通过元数据生成的代理一样。

提示:

如果以这种方式创建代理类,通信通道就默认为在1分钟后超时,导致通信错误。使连接一直处于激活状态有许多方式,但这些都超出了本章的讨论范围。

以这种方式创建代理类是一种非常有用的技术,可以快速生成客户应用程序。

35.3.2  自存储的WCF服务

本章前面介绍了存储在Web服务器上的WCF服务。它们可以在Internet上通信,但对于本地网络通信而言,这并不是最高效的方式。一方面,需要用计算机上的Web服务器存储服务,另一方面,在应用程序的体系结构上出现一个独立的WCF服务可能并不合适。

因此应使用自存储的WCF服务。自存储的WCF服务存在于创建它的进程中,而不存在于特别建立的主机应用程序(如Web服务器)的进程中。这样,就可以使用控制台应用程序或Windows应用程序存储服务了。

要建立自存储的WCF服务,需要使用 System.ServieceModel.ServieceHost类。用要存储的服务类型或服务类的一个实例来实例化这个类。通过属性或方法可以配置 服务主机,也可以通过配置文件来配置。实际上,主机进程(如Web服务器)使用ServieceHost实例完成该存储任务。自存储时,区别是直接与这个 类交互操作。但是,在主机应用程序的app.config文件中,<system.servieceModel>段中的配置使用的语法与本章 前面的配置段中的相同。

可以通过任意协议提供自存储的WCF服务,但是一般在这种类型的应用程序中使用TCP或指定管道绑定。通过HTTP访问的服务常常位于Web服务器进程中,因为可以获得Web服务器提供的额外功能,如安全性等。

如果要存储MyService服务,可以使用下面的代码创建ServieceHost的一个实例:

ServiceHost host = new ServiceHost(typeof(MyService));

如果要存储MyService的实例MyServiceObject,可以编写如下代码,创建ServieceHost的一个实例:

MyService myServiceObject = new MyService();

ServiceHost host = new ServiceHost(myServiceObject);

注意,只有配置了服务,使调用总是可以路由到同 一个对象实例上,才能使用后一种技术。为此,必须给服务类应用ServieceBehaviorAttribute属性,将这个属性的 InstanceContextMode特性设置为InstanceContextMode.Single。

创建了ServieceHost实例后,就可以通过属性配置服务、其端点和绑定。另外,如果把配置放在.config文件中,ServieceHost实例就会自动配置。

有了配置好的ServieceHost实例后, 为了开始存储服务,使用ServieceHost.Open()方法。同样,通过ServieceHost.Close()方法可以停止存储服务。第一次 存储TCP绑定的服务时,如果启用它,可能会收到Windows防火墙服务发出的一个警告,因为它阻塞了默认的TCP端口。必须给这个服务打开TCP端 口,才能开始监听该端口。

下面的示例使用自存储技术通过WCF服务提供WPF应用程序的一些功能。

试试看:自存储的WCF服务

(1) 创建一个新的WPF应用程序Ch35Ex03,将其保存在C:\BegVCSharp\Chapter35目录下。

(2) 使用Add New Item向导给项目添加一个新的WCF服务AppControlService。

(3) 修改Window1.xaml中的代码,如下所示:

<Window

xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

x:Class="Ch35Ex03.Window1"

Title="Solar Evolution" Height="450" Width="430"Loaded="Window_Loaded"

Closing="Window_Closing">

<Grid Height="400" Width="400" HorizontalAlignment="Center"

VerticalAlignment="Center">

<Rectangle Fill="Black" RadiusX="20" RadiusY="20" StrokeThickness="10">

<Rectangle.Stroke >

<LinearGradientBrush EndPoint="0.358,0.02" StartPoint="0.642,0.98">

<GradientStop Color="#FF121A5D" Offset="0"/>

<GradientStop Color="#FFB1B9FF" Offset="1"/>

</LinearGradientBrush >

</Rectangle.Stroke >

</Rectangle >

<Ellipse Name="AnimatableEllipse" Stroke="{x:Null}" Height="0"

Width="0"HorizontalAlignment="Center" VerticalAlignment="Center">

<Ellipse.Fill >

<RadialGradientBrush >

<GradientStop Color="#FFFFFFFF" Offset="0"/>

<GradientStop Color="#FFFFFFFF" Offset="1"/>

</RadialGradientBrush>

</Ellipse.Fill>

<Ellipse.BitmapEffect>

<OuterGlowBitmapEffect GlowColor="#FFFFFFFF" GlowSize="16"/>

</Ellipse.BitmapEffect>

</Ellipse>

</Grid>

</Window>

(4) 修改Window1.xaml.cs中的代码,如下所示:

...

using System.Windows.Shapes;

using System.ServiceModel;

using System.Windows.Media.Animation;

namespace Ch35Ex03

{

/// < summary >

/// Interaction logic for Window1.xaml

/// < /summary >

public partial class Window1 : Window

{

private AppControlService service;

private ServiceHost host;

public Window1()

{

InitializeComponent();

}

private void Window_Loaded(object sender, RoutedEventArgs e)

{

service = new AppControlService(this);

host = new ServiceHost(service);

host.Open();

}

private void Window_Closing(object sender,

System.ComponentModel.CancelEventArgs e)

{

host.Close();

}

internal void SetRadius(double radius, string foreTo, TimeSpan duration)

{

if (radius > 200)

{

radius = 200;

}

Color foreToColor = Colors.Red;

try

{

foreToColor = (Color)ColorConverter.ConvertFromString(foreTo);

}

catch

{

// Ignore color conversion failure.

}

Duration animationLength = new Duration(duration);

DoubleAnimation radiusAnimation = new DoubleAnimation(

radius * 2, animationLength);

ColorAnimation colorAnimation = new ColorAnimation(

foreToColor, animationLength);

AnimatableEllipse.BeginAnimation(Ellipse.HeightProperty,

radiusAnimation);

AnimatableEllipse.BeginAnimation(Ellipse.WidthProperty,

radiusAnimation);

((RadialGradientBrush)AnimatableEllipse.Fill).GradientStops[1]

.BeginAnimation(GradientStop.ColorProperty, colorAnimation);

}

}

}

(5) 修改IAppControlService.cs中的代码,如下所示:

[ServiceContract]

public interface IAppControlService

{

[OperationContract]

void SetRadius(int radius, string foreTo, int seconds);

}

(6) 修改AppControlService.cs中的代码,如下所示:

[ServiceBehavior(InstanceContextMode=InstanceContextMode.Single)]

public class AppControlService : IAppControlService

{

private Window1 hostApp;

public AppControlService(Window1 hostApp)

{

this.hostApp = hostApp;

}

public void SetRadius(int radius, string foreTo, int seconds)

{

hostApp.SetRadius(radius, foreTo, new TimeSpan(0, 0, seconds));

}

}

(7) 修改app.config中的代码,如下所示:

<configuration>

<system.serviceModel>

<services>

<service name="Ch35Ex03.AppControlService">

<endpoint address="net.tcp://localhost:8081/AppControlService"

binding="netTcpBinding" contract="Ch35Ex03.IAppControlService"/ >

</service>

</services>

</system.serviceModel>

</configuration>

(8) 在项目中添加一个新的控制台应用程序Ch35Ex03Client。

(9) 配置解决方案,使之有多个启动项目,让两个项目同时启动。

(10) 在Ch35Ex03Client项目中添加对System.ServieceModel.dll和Ch35Ex03的引用。

(11) 修改Program.cs中的代码,如下所示:

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using Ch35Ex03;

using System.ServiceModel;

namespace Ch35Ex03Client

{

class Program

{

static void Main(string[] args)

{

Console.WriteLine("Press enter to begin. ");

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值