前面介绍了基础知识,下面开始编写一些代码。本节首先看一个在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服务客户机
下面详细介绍合同。
前面的示例说明了WCF体系结构如何便于给WCF服务定义合同,包括类、接口和属性。本节将深入介绍这种技术。
要给服务定义数据合同,需要把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消息中 |
把System.ServieceModel.ServieceContractAttribute属性应用于接口定义,就定义了服务合同。表35-4中所示的属性可用于定制服务合同。
表 35-4
属 性 |
用 法 |
Name |
按照WSDL中<portType>元素中的定义,指定服务合同的名称 |
Namespace |
定义WSDL中<portType>元素使用的服务合同的命称空间 |
ConfigurationName |
在配置文件中使用的服务合同名称 |
HasProtectionLevel |
指定服务使用的消息是否有明确定义的保护级别。保护级别允许签名消息,或者签名和加密消息 |
ProtectionLevel |
保护级别,用于保护消息 |
SessionMode |
确定是否为消息启用会话。如果启用会话,就可以确保关联上发送给服务的不同端点的消息,即它们使用同一个服务实例,因此可以共享状态 |
CallbackContract |
对于双向消息传输,客户机提供了合同和服务。这是因为,如前所述,双向通信中的客户机也用作服务器。这个属性允许指定客户机使用的合同 |
在定义服务合同的接口中,应用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 |
同上,但为操作的响应指定动作名称 |
前面的示例中没有使用消息合同规范。如果使用消 息合同,就应定义一个表示消息的类,再给类应用MessageContractAttribute属性。接着给这个类的成员应用Message Body MemberAttribute、MessageHeaderAttribute或MessageHeaderArrayAttribute属性。所有这 些属性都在System. ServieceModel名称空间中。如果要高度控制WCF服务使用的SOAP消息,就不要使用消息合同,所以这里不详细讨论它。
如果客户应用程序可以使用特定的异常类型,如定制异常,就可以给可能生成该异常的操作应用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分钟后超时,导致通信错误。使连接一直处于激活状态有许多方式,但这些都超出了本章的讨论范围。
以这种方式创建代理类是一种非常有用的技术,可以快速生成客户应用程序。
本章前面介绍了存储在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. ");
sty