WCF快速启动
定义服务契约
构建WCF应用程序的第一步是创建服务契约。现在,可以肯定地说,契约是表示消息应用系统外形的主要方式。外形,是指服务展示的操作,操作生成、使用的消息schema和每个操作实现的消息交换模式。总之,契约定义了消息应用生成和使用的东西。大多数契约是带有WCF API定义的属性标记的类型定义。
[System.ServiceModel.ServiceContract]
public interface IHelloWCF
{
[System.ServiceModel.OperationContract]
void Say(string input);
}
定义地址和绑定
定义侦听请求消息的地址要使用System.Uri类型,定义如何与其他消息参与者交换消息要使用System.ServiceModel.Channels.Binding类型,或者这些类型的继承类型。
static void Main(string[] args)
{
//定义侦听消息的地址
Uri address = new Uri("http://localhost:8000/IHellowWCF");
//定义如何交换消息
BasicHttpBinding binding = new BasicHttpBinding();
}
创建一个终结点并启动侦听
接下来要使用地址(address)、绑定(binding)和契约(contract)来构建一个终结点,并在此终结点上侦听发送来的消息。通常来说,WCF接收程序可以构建和使用多个终结点,并且每个终结点都需要一个地址、一个绑定和契约。System.ServiceModel.ServiceHost类型构建和托管终结点,并管理接收应用底层结构的其他部分,如线程和对象的生命周期。
static void Main(string[] args)
{
//定义侦听消息的地址
Uri address = new Uri("http://localhost:8000/IHellowWCF");
//定义如何交换消息
BasicHttpBinding binding = new BasicHttpBinding();
//实例化ServiceHost,传递服务类型
ServiceHost svc = new ServiceHost(typeof(IHelloWCF));
//增加终结点、绑定和契约
svc.AddServiceEndpoint(typeof(IHelloWCF),binding,address);
//开启侦听
svc.Open();
//表示应用程序准备接收消息
//保持应用程序不会立即退出
Console.WriteLine("The HelloWCF receiving application is ready");
Console.ReadLine();
//关闭宿主
svc.Close();
}
映射接收的消息到HelloWCF的成员
//实现服务契约IHelloWCF
sealed class HelloWCF : IHelloWCF
{
//表示创建HelloWCF
HelloWCF() { Console.WriteLine("HelloWCF object Created"); }
//接收到的消息会立即分发到服务实例的方法上
public void Say(string input)
{
Console.WriteLine("Message received,the body contains:{0}",input);
}
}
static void Main(string[] args)
{
//定义侦听消息的地址
Uri address = new Uri("http://localhost:8000/IHellowWCF");
//定义如何交换消息
BasicHttpBinding binding = new BasicHttpBinding();
//实例化ServiceHost,传递服务类型,消息将由此类型对象处理
ServiceHost svc = new ServiceHost(typeof(HelloWCF));
//增加终结点、绑定和契约
svc.AddServiceEndpoint(typeof(IHelloWCF), binding, address);
//开启侦听
svc.Open();
//表示应用程序准备接收消息
//保持应用程序不会立即退出
Console.WriteLine("The HelloWCF receiving application is ready");
Console.ReadLine();
//关闭宿主
svc.Close();
}
改变HelloWCF类型定义可以让消息的基础结构分发接收的消息到服务实例的Say操作,因此会在控制台界面上输出一条简单的语句。
编译、运行和检验接收者
这个时候服务端就算完成了,接下来编译代码运行程序,之后检验接收者是否确实在侦听,可使用netstat -a -b命令查看端口是否被侦听。
向接收者发送消息
发送消息的基础结构也需要要依靠地址、绑定和契约,这与接收消息的基础结构类似。很典型的是,发送者的重要职责就是使用与接收者兼容的地址、绑定和契约。发送者不使用ServiceHost类型,而是使用ChannelFactory<T>类型(T是服务契约)。用ChannelFactory<T>类型构建发送消息的基础结构和用ServiceHost构建接收消息的基础结构类似。以下代码演示了如何使用EndpointAddress类型和ChannelFactory<T>构建发送基础结构。
static void Main(string[] args)
{
//定义侦听消息的地址
Uri address = new Uri("http://localhost:8000/IHellowWCF");
//定义如何交换消息
BasicHttpBinding binding = new BasicHttpBinding();
//实例化ServiceHost,传递服务类型
ServiceHost svc = new ServiceHost(typeof(HelloWCF));
//增加终结点、绑定和契约
svc.AddServiceEndpoint(typeof(IHelloWCF),binding,address);
//开启侦听
svc.Open();
//表示应用程序准备接收消息
//保持应用程序不会立即退出
Console.WriteLine("The HelloWCF receiving application is ready");
//发送者代码开始
//使用绑定和地址创建一个channelFactory<T>实例
ChannelFactory<IHelloWCF> factory = new ChannelFactory<IHelloWCF>(binding, new EndpointAddress(address));
//使用工厂创建代理
IHelloWCF proxy = factory.CreateChannel();
//使用代理发送消息给接收者
proxy.Say("Hi there WCF");
//发送代码结束
Console.ReadLine();
//关闭宿主
svc.Close();
}
编译、运行和检验发送者
既然已经完成了发送和接收基础结构代码,现在应该是编译和运行程序的时候。正如我们期望的一样,程序执行步骤如下:
1.为接收来自http://localhost:8000/IHelloWCF的消息构建基础结构。
2.开始在http://localhost:8000/IHelloWCF上侦听消息。
3.构建发送到http://localhost:8000/IHelloWCF消息的基础结构。
4.生成和发送消息到http://localhost:8000/IHelloWCF。
5.接收消息,实例化一个HelloWCF对象,分发消息到服务实例的Say方法上。
看消息
对于开发者来说,WCF应用程序看起来和面向对象或组件的应用系统一样。在运行时,WCF应用系统要生成、发送和接收消息,同样也要处理消息。通过修改Say方法的实现,我们能看到WCF基础结构的消息:
public void Say(string input)
{
Console.WriteLine("Message received,the body contains:{0}",input);
Console.WriteLine(OperationContext.Current.RequestContext.RequestMessage.ToString());
}
修改Say方法后的输出如下
输出的SOAP消息,消息的body部分包含了传递给局部变量的channel上Say方法的字符串。
暴露元数据
WS-MetadataExchange规范规定了发送者和接收者在与平台无关时如何交换这些数据信息。有这样一个需求,即发送者询问接收者的终结点并提取元数据,然后使用这些元数据构建能发送给接收终结点信息的基础结构。
如果决定暴露系统的元数据,可以构建一个暴露元数据的终结点,而构建元数据终结点的方式和其他终结点非常相似:使用地址、绑定和契约。
构建元数据终结点的第一步是修改ServiceHost到可以托管元数据的状态,通过System.ServiceModel.Description.ServiceMetadataehavior对象添加到ServiceHost行为集合里。行为是WCF基础结构用来改变本地消息处理的特定信息。下面代码演示了如何添加ServiceMetadataBehavior对象到活动的ServiceHost对象:
//传递类型去实例化ServiceHost
//当应用程序接收到消息
ServiceHost svc = new ServiceHost(typeof(IHelloWCF), address);
//创建服务元数据行为
ServiceMetadataBehavior metadata = new ServiceMetadataBehavior();
metadata.HttpGetEnabled = true;
//添加到servicehost description
svc.Description.Behaviors.Add(metadata);
下一步就是为元数据终结点定义Binding。元数据绑定的对象模型与其他绑定区别很大,可以通过调用工厂方法上的System.ServiceModel.Description.MetadataExchangeBindings类型创建元数据绑定,如下所示
//传递类型去实例化ServiceHost
//当应用程序接收到消息
ServiceHost svc = new ServiceHost(typeof(IHelloWCF), address);
//创建服务元数据行为
ServiceMetadataBehavior metadata = new ServiceMetadataBehavior();
metadata.HttpGetEnabled = true;
//添加到servicehost description
svc.Description.Behaviors.Add(metadata);
//创建TCP元数据绑定
Binding mexBinding = MetadataExchangeBindings.CreateMexTcpBinding();
元数据可以通过多种传输协议传递,并且WS-MetadataExchange说明了这个灵活性。在我们的例子中因为使用的是TCP传输,使用必须确定元数据地址使用了TCP地址格式,如下
//创建元数据交换侦听地址
Uri address = new Uri("net.tcp://localhost:5000/IHellowWCF/Mex");
既然定义了暴露元数据终结点需要的地址和绑定,就要添加终结点到ServiceHost上。当添加元数据终结点时,要使用WCF API定义名为System.ServiceModel.Description.IMetadataExchange的服务契约。以下代码演示了如何使用适当的地址、绑定和契约添加一个元数据终结点到ServiceHost上。
//传递类型去实例化ServiceHost
//当接收到消息
ServiceHost svc = new ServiceHost(typeof(IHelloWCF));
//创建服务元数据行为
ServiceMetadataBehavior metadata = new ServiceMetadataBehavior();
metadata.HttpGetEnabled = true;
//添加到servicehost description
svc.Description.Behaviors.Add(metadata);
//创建TCP元数据绑定
Binding mexBinding = MetadataExchangeBindings.CreateMexTcpBinding();
//创建元数据交换侦听地址
Uri mexAddress = new Uri("net.tcp://localhost:5000/IHellowWCF/Mex");
//添加元数据终结点
svc.AddServiceEndpoint(typeof(IMetadataExchange), mexBinding, mexAddress);
使用元数据
Microsoft .NET Framework SDK安装了一个功能强大的工具,名字为svcutil.exe,它的一个功能就是询问一个运行的消息应用并基于获得的信息生成代理。Svcutil.exe会创建两个文件:HelloWCFProxy.cs和output.config。如果检查HelloWCFProxy.cs文件,就会看到svcutill.exe产生了一个包含IHelloWCF、IHelloWCFChannel和HelloWCFClient的代码文件。
从外部剖析WCF
构建一个接收程序可以简单地理解我使用一个地址、绑定和契约向接收终结点发送消息。如果要修改发送者或接收者的处理过程,可以使用自己的行为配置或使用WCF自带行为(比如增加元数据支持)。图4-1所示为Endpoint、Address、Binding、Contract和Behavior之间的关系。
图 4-1 Endpoint、Address、Binding、Contract和Behavior
地址
所有发送或接收消息的程序必须在终结点上使用某个地址。WCF发送基础结构依赖System.ServiceModel.EndpointAddress类型发送消息给最终接收者。System.ServiceModel.EndpointAddress类型是对WS-Address规范中终结点参考规范的CLR抽象,发送者使用该类型去为请求添加终结点信息,并与接收终结点建立连接。
绑定
绑定是表示消息应用如何处理、发送和接收消息的主要方式。它表示传输、WS-*协议、安全需求和终结点的主要方式。WCF包含9种覆盖传输、WS-*协议、安全需求、事务需求的绑定。如果这些绑定不能满足需求,还可以定义符合特定需求的绑定。
所有绑定类型都是System.ServiceModel.Channels.Binding的子类型,因此它们都有相同的属性。一个共同的属性就是它们维护着一个私有System.ServiceModel.Channels.BindingElement对象的列表。一个BindingElement是消息交换一个特定方面的抽象,像传输或WS-*协议。
static void Main(string[] args)
{
List<Binding> bindings = new List<Binding>();
bindings.Add(new BasicHttpBinding());
bindings.Add(new NetNamedPipeBinding());
bindings.Add(new NetTcpBinding());
bindings.Add(new WSDualHttpBinding());
bindings.Add(new WSHttpBinding());
bindings.Add(new NetMsmqBinding());
bindings.Add(new WSFederationHttpBinding());
ShowBindingElements(bindings);
Console.ReadKey();
}
private static void ShowBindingElements(List<Binding> bindings)
{
foreach(Binding binding in bindings)
{
Console.WriteLine("Showing Binding Elements for {0}",binding.GetType().Name);
foreach(BindingElement element in binding.CreateBindingElements())
{
Console.WriteLine("\t{0}",element.GetType().Name);
}
}
}
程序输出如下:
契约
契约把面向对象的结构映射为消息结构。契约定义了程序里的终结点、终结点使用的消息交换模式和终结点处理的消息结构。WCF里定义了三种契约类型:服务契约、数据契约和消息契约。服务契约描述了终结点的操作。描述包含名称、消息交换模式、会话规格信息、请求和响应的消息头、每个操作的安全信息。数据契约,换句话说,就是映射消息体到一个或多个操作。消息契约映射消息体和消息头到一个或多个操作。
服务契约
服务契约表示终结点展示的服务操作,以及这些操作被消息交换中的发送者和接收者使用。接收程序可以使用服务契约去构建侦听请求的基础结构。发送程序可以使用服务契约构建发送到消息接收终结点的消息的基础结构。服务契约里的信息包含每个操作的名字、操作参数、Action头与操作、操作会话规范信息的关联。
在元素级别上,服务契约是一个被ServiceContractAttribute或OperationContractAttribute属性标记的类或接口。OperationContractAttribute标记在方法上。
数据契约
数据契约映射.NET Framework类型到信息体。数据契约也是一个标记类型定义,使用的属性就是DataContractAttribute和DataMemberAttribute。大部分时间,服务契约使用数据契约,如下所示:
[ServiceContract]
interface ISomeServiceContract
{
[OperationContract]
void SomeOperation(SomeDataContract info);//注意参数类型
}
[DataContract]
sealed class SomeDataContract
{
[DataMember]
int number;
string status;
public int Number{
get{ return number; }
set{ number = value;}
}
[DataMember]
public string Status{
get{ return status; }
set{ status = value; }
}
SomeDataContract(int number):this(number,null){
}
SomeDataContract(int number,string status){
this.number = number;
this.status = status;
}
}
消息契约
消息契约映射.NET Framework类型到消息结构上。如果XML是消息结构,那么消息契约会映射.NET Framework类型到消息的schema上。这包含消息头和消息体,如下所示:
[ServiceContract]
interface ISomeServiceContract
{
[OperationContract]
void SomeOperation(SomeDataContract info);//注意参数类型
void SomeOtherOperation(SomeMessageContract info);//注意参数类型
}
[DataContract]
internal sealed class SomeDataContract
{
[DataMember]
int number;
string status;
public int Number{
get{ return number; }
set{ number = value;}
}
[DataMember]
public string Status{
get{ return status; }
set{ status = value; }
}
internal SomeDataContract(int number):this(number,null){
}
internal SomeDataContract(int number,string status){
this.number = number;
this.status = status;
}
}
[MessageContract]
internal sealed class SomeMessageContract
{
SomeMessageContract() { }//比如包含默认的构造函数
[MessageHeader]
int SomeNumber;
[MessageBodyMember]
SomeDataContract messageBody;
public SomeMessageContract(int someNumber)
{
SomeNumber = someNumber;
messageBody = new SomeDataContract(someNumber);
}
}
从内部剖析WCF
当看完整个地址、绑定和契约的内容时,发现实际上只有两个主要架构层的基础结构。这两个层次的名字分别为服务模型层和通道层。服务模型层是用户代码和通道层之间的桥梁。通道层,换句话说,是做了真正的消息相关的工作,是了解特定传输方式和WS-*消息编排的层。服务模型层调用发送者上的代理和接收者上的分发器有不同的作用,尽管它们是相同架构的不同部分。代理负责创建消息,并发送给通道层。分发器负责反序列化接收到消息、实例化对象并发反序列化的消息内容到此对象。
图4-2说明了服务层和通道层是如何协同工作的。
图 4-2 服务层和通道层
在消息接收者上,地址告诉通道侦听请求消息的地址。在发送者上,地址告诉通道消息接收者的地址。绑定,就是创建通道层的工厂对象的集合。契约是消息序列化和反序列化使用的,它们同样会被用来确定接收程序的消息交换模式。通常来说,契约是一个服务模型层的结构。行为可以影响服务模型层和通道层。如图4-3所示。
图 4-3 ABC 如何影响ServiceModel层和通道层