在本节,我要建立一个HelloWCF应用程序以向计算机科学之神表示我们的敬意。在建立这个应用后,我们分成不同的部分细看。为例子尽量简单明了,我们会把发送者和接受放在一个控制台应用里。让我们现在就开始在控制台应用里构建需要的基础架构。
// File: HelloWCFApp.csusing System; sealed class HelloWCF { static void Main(){ }}
定义服务契约
构建
HelloWCF应用的第一步是创建服务契约。第9章里会详细介绍契约,现在,可以肯定地说,契约式表示消息应用外形
的主要方式。对于外形,我是意思是说我们服务暴露的操作,操作生成、使用的消息schema和每个操作实现的消息交换模式(MEP)。
总之,契约定义了我们消息应用生成和使用的东西。大多数契约是带有
WCF API
定义的属性标记的类型定义。
在我们的例子里,服务契约是一个带有System.ServiceModel.ServiceContractAttribute 和System.ServiceModel.OperationContractAttribute标记的接口,如下所示:
// File: HelloWCFApp.cs
[ServiceContract]
public interface IHelloWCF {
[OperationContract]
void Say(String input);
}
[ServiceContract]
public interface IHelloWCF {
[OperationContract]
void Say(String input);
}
在高层次上,我们的服务契约表示我们接收消息的应用包含一个名字为Say的操作,并且这个操作接收一个String类型的参数
和void返回类型。发送消息的应用可以用它来构造和发送消息给接收程序。既然我们已经定义的服务契约,那就到了该定义
接收程序侦听地址和如何与其它消息参与者交换消息的时候了。
定义地址和绑定
定义侦听请求消息的地址需要使用
System.Uri类型,定义如何与其它消息参与者交换消息需要我们使用System.ServiceModel.Channels.Binding类型。或者这些类型的继承类型。下面的代码说明了如何在我们的应用里使用
Uri和Binding类型。
// File: HelloWCFApp.csstatic void Main(){ // define where to listen for messages定义侦听消息的地址 Uri address = new Uri("http://localhost:8000/IHelloWCF"); // define how to exchange messages定义如何交换消息 BasicHttpBinding binding = new BasicHttpBinding();}
注意局部变量address使用的是HTTP格式的统一资源标识符(URI)。选择这个地址强制要求我们使用HTTP传输。更高层次
上,绑定是指定传输、消息编排和消息编码的主要方式。局部变量binding是BasicHttpBinding类型的实例。和你从名字
看到的一样,BasicHttpBinding创建的是一个用于HTTP传输的消息架构。
创建一个终结点并启动侦听
接下来我们要使用地址(
address)、绑定(binding)和契约(contract)来构建一个终结点(endpoint)并在此终结点上
侦听发送进来的消息。通常来说,一个WCF接受程序可以构建和使用多个终结点,并且每个终结点都需要一个地址、一个绑定和一个契约。System.ServiceModel.ServiceHost类型构建和托管终结点,并管理接受应用底层结构的其他部分,比如线程
和对象的生命周期。下面代码块演示了如何实例化ServiceHost,如何添加终结点和如何开始侦听进入的消息:
// File: HelloWCFApp.cs static void Main(){ // define where to listen for messages定义侦听消息的地址 Uri address = new Uri("http://localhost:4000/IHelloWCF"); // define how to exchange messages定义如何交换消息 BasicHttpBinding binding = new BasicHttpBinding(); // instantiate a ServiceHost, passing the type to instantiate实例化ServiceHost,传递服务类型 // when the application receives a message ServiceHost svc = new ServiceHost(typeof(HelloWCF)); // add an endpoint, passing the address, binding, and contract增加终结点、绑定和契约 svc.AddServiceEndpoint(typeof(IHelloWCF), binding, address); // begin listening开始侦听 svc.Open(); // indicate that the receiving application is ready and指示应用准备接受消息 // keep the application from exiting immediately保持应用程序不会立即退出 Console.WriteLine("The HelloWCF receiving application is ready"); Console.ReadLine(); // close the service host关闭宿主 svc.Close(); }
注意调用ServiceHost的构造函数的参数。ServiceHost构造函数被重载多次,在某些形式上,每个重载接受的都是WCF
底层结构分发请求消息的对象的类型定义。前面代码所示ServiceHost构造函数表示消息基础结构会分发接受到的消息给
HelloWCF服务类型的一个实例。
也会发现,前面代码里调用了
svc.AddServiceEndpoint 和svc.Open。AddServiceEndpoint实例方法设置ServiceHost
对象的属性,这样它将使用地址、绑定和契约参数执行的行为来侦听消息。要着重指出的是AddServiceEndpoint方法没有
开始循环侦听;它仅仅是简单地改变了ServiceHost对象的状态(第10章会详细讨论)。ServiceHost实例的Open方法构建
了消息基础结构,并开始循环侦听。Open方法会验证ServiceHost对象的状态,从它的状态里构建终结点,并且开始侦听。
映射接受的消息到HelloWCF的成员
在目前状态,我们编译程序,当程序试图构建一个终结点的时候,会出现一个异常:InvalidOperationException。原因一
目了然:在
ServiceHost类型的构造函数里,我们传递了HelloWCF作为参数,因此,这就表示消息基础结构要分发消息给
我们的HelloWCF对象。因此,必然存在消息到服务成员的映射关系。最简单的创建映射的方式就是使HelloWCF服务类实现
服务契约IHelloWCF。
// File: HelloWCFApp.cs
using System;
using System.ServiceModel;
using System.ServiceModel.Channels;
// implement the IHelloWCF service contract
sealed class HelloWCF : IHelloWCF {
// indicate when a HelloWCF object is created
HelloWCF() { Console.WriteLine( "HelloWCF object created"); }
static void Main(){
// define where to listen for messages
Uri address = new Uri( "http://localhost:4000/IHelloWCF");
// define how to exchange messages
BasicHttpBinding binding = new BasicHttpBinding();
// instantiate a ServiceHost, passing the type to instantiate
// when the application receives a message
ServiceHost svc = new ServiceHost(typeof(HelloWCF));
// add an endpoint, passing the address, binding, and contract
svc.AddServiceEndpoint(typeof(IHelloWCF), binding, address);
// begin listening
svc.Open();
// indicate that the receiving application is ready and
// keep the application from exiting immediately
Console.WriteLine("The HelloWCF receiving application is ready");
// wait for incoming messages
Console.ReadLine();
// close the service host
svc.Close();
}
// received messages are dispatched to this instance
// method as per the service contract
public void Say(String input){
Console.WriteLine("Message received, the body contains: {0}", input);
}
}
[ServiceContract]
public interface IHelloWCF {
[OperationContract]
void Say(String input);
}
using System;
using System.ServiceModel;
using System.ServiceModel.Channels;
// implement the IHelloWCF service contract
sealed class HelloWCF : IHelloWCF {
// indicate when a HelloWCF object is created
HelloWCF() { Console.WriteLine( "HelloWCF object created"); }
static void Main(){
// define where to listen for messages
Uri address = new Uri( "http://localhost:4000/IHelloWCF");
// define how to exchange messages
BasicHttpBinding binding = new BasicHttpBinding();
// instantiate a ServiceHost, passing the type to instantiate
// when the application receives a message
ServiceHost svc = new ServiceHost(typeof(HelloWCF));
// add an endpoint, passing the address, binding, and contract
svc.AddServiceEndpoint(typeof(IHelloWCF), binding, address);
// begin listening
svc.Open();
// indicate that the receiving application is ready and
// keep the application from exiting immediately
Console.WriteLine("The HelloWCF receiving application is ready");
// wait for incoming messages
Console.ReadLine();
// close the service host
svc.Close();
}
// received messages are dispatched to this instance
// method as per the service contract
public void Say(String input){
Console.WriteLine("Message received, the body contains: {0}", input);
}
}
[ServiceContract]
public interface IHelloWCF {
[OperationContract]
void Say(String input);
}
改变HelloWCF的类型定义会使得消息的基础结构分发接受到的消息到服务实例的
Say操作上,因此会在控制台界面上输出
一个简单的语句。
编译、运行和检验接受者
我们现在准备使用下面的命令行编译并运行这个应用:
C:\temp>csc /nologo /r:"c:\WINDOWS\Microsoft.Net\Framework\v3.0\Windows Communication
Foundation\System.ServiceModel.dll" HelloWCFApp.cs
C:\temp>HelloWCFApp.exe
The HelloWCF receiving application is ready
Foundation\System.ServiceModel.dll" HelloWCFApp.cs
C:\temp>HelloWCFApp.exe
The HelloWCF receiving application is ready
此时,接受消息的应用在被动地等待请求消息的到来。我们是用
netstat.exe可以检查一下应用是否确实在侦听,如下所示:
其实在这个例子里你可以看到更多的输入信息,但是你将会看到2行与此类似的结果(我的电脑名字是Kermit)。c:\temp>netstat –a –bTCP kermit:4000 0.0.0.0:0 LISTENING 1104[HelloWCFApp.exe]
向接受者发送消息
发送消息的基础结构也需要依靠地址、绑定和契约,这与接收消息的基础结构类似。非常典型的是发送者使用的地址、绑定和
契约和接受者。
与接受者不同,发送代码使用的是不同的类型。概念上,这样非常有用,因为发送者和接受者在消息交换中扮演着不同的角色。放弃直接使用
Uri类型,绝大多数接受者使用System.Service-Model.EndpointAddress类型去表示消息发送的目标。你将在第5章:消息里看到,EndpointAddress类型是WCF对于WS-Addressing 终结点参考的抽象。此外,发送者不使用ServiceHost类型,而是使用ChannelFactory<T>类型(T是服务契约类型)。ChannelFactory<T>类型构建发送消息的基础结构和ServiceHost构建接受消息的基础结构类似。下面的代码演示了如何使用EndpointAddress类型和ChannelFactory<T>构建发送基础结构。
// File: HelloWCFApp.cs
using System;
using System.ServiceModel;
using System.ServiceModel.Channels;
// implement the IHelloWCF service contract
sealed class HelloWCF : IHelloWCF {
// indicate when a HelloWCF object is created
HelloWCF() { Console.WriteLine( "HelloWCF object created"); }
static void Main(){
// define where to listen for messages
Uri address = new Uri( "http://localhost:4000/IHelloWCF");
// define how to exchange messages
BasicHttpBinding binding = new BasicHttpBinding();
// instantiate a ServiceHost, passing the type to instantiate
// when the application receives a message
ServiceHost svc = new ServiceHost(typeof(HelloWCF));
// add an endpoint, passing the address, binding, and contract
svc.AddServiceEndpoint(typeof(IHelloWCF), binding, address);
// begin listening
svc.Open();
// indicate that the receiving application is ready and
// keep the application from exiting immediately
Console.WriteLine("The HelloWCF receiving application is ready");
// begin the sender code发送者代码开始
// create a channelFactory<T> with binding and address
ChannelFactory<IHelloWCF> factory =
new ChannelFactory<IHelloWCF>(binding,
new EndpointAddress(address));
// use the factory to create a proxy使用工厂创建代理
IHelloWCF proxy = factory.CreateChannel();
// use the proxy to send a message to the receiver使用代理发送消息给接受者
proxy.Say("Hi there WCF");
// end the sender code发送者代码结束
Console.ReadLine();
// close the service host
svc.Close();
}
// received messages are dispatched to this instance
// method as per the service contract
public void Say(String input){
Console.WriteLine("Message received, the body contains: {0}", input);
}
}
[ServiceContract]
public interface IHelloWCF {
[OperationContract]
void Say(String input);
}
using System;
using System.ServiceModel;
using System.ServiceModel.Channels;
// implement the IHelloWCF service contract
sealed class HelloWCF : IHelloWCF {
// indicate when a HelloWCF object is created
HelloWCF() { Console.WriteLine( "HelloWCF object created"); }
static void Main(){
// define where to listen for messages
Uri address = new Uri( "http://localhost:4000/IHelloWCF");
// define how to exchange messages
BasicHttpBinding binding = new BasicHttpBinding();
// instantiate a ServiceHost, passing the type to instantiate
// when the application receives a message
ServiceHost svc = new ServiceHost(typeof(HelloWCF));
// add an endpoint, passing the address, binding, and contract
svc.AddServiceEndpoint(typeof(IHelloWCF), binding, address);
// begin listening
svc.Open();
// indicate that the receiving application is ready and
// keep the application from exiting immediately
Console.WriteLine("The HelloWCF receiving application is ready");
// begin the sender code发送者代码开始
// create a channelFactory<T> with binding and address
ChannelFactory<IHelloWCF> factory =
new ChannelFactory<IHelloWCF>(binding,
new EndpointAddress(address));
// use the factory to create a proxy使用工厂创建代理
IHelloWCF proxy = factory.CreateChannel();
// use the proxy to send a message to the receiver使用代理发送消息给接受者
proxy.Say("Hi there WCF");
// end the sender code发送者代码结束
Console.ReadLine();
// close the service host
svc.Close();
}
// received messages are dispatched to this instance
// method as per the service contract
public void Say(String input){
Console.WriteLine("Message received, the body contains: {0}", input);
}
}
[ServiceContract]
public interface IHelloWCF {
[OperationContract]
void Say(String input);
}
注意到我们调用
ChannelFactory<T>实例的CreateChannel方法,并且使用其返回的类型调用我们服务契约的方法。
更高层次上,ChannelFactory<T>对象是一个可以制造产生和发送消息给接受者(因此需要在构造函数里传递绑定和地址)
的基础结构的类型。ChannelFactory<T>实例的CreateChannel方法实际创建的是发送基础结构,并且通过实现服务契约
的一个对象返回这个基础结构的引用。我们可以通过调用服务契约的方法来与发送基础结构交互,这些都会在本章后面,和
第
6章:通道里详细介绍。
编译、运行和检验发送者
既然我们已经完成了发送和接受基础结构代码,现在应该是编译和运行程序的时候了,如下所示:
c:\temp>csc /nologo /r:"c:\WINDOWS\Microsoft.Net\Framework\v3.0\Windows Communication
Foundation\System.ServiceModel.dll" HelloWCFApp.cs
c:\temp>HelloWCFApp.exe
The HelloWCF receiving application is ready
HelloWCF object created
Message received, the body contains: HelloWCF!
如期望的一样,我们的程序执行步骤如下:
1. 为接受来自http: //localhost:4000/IHelloWCF的消息构建基础结构。
2. 开始在http: //localhost:4000/IHelloWCF上侦听消息。
3. 构建发送到http: //localhost:4000/IHelloWCF消息的基础结构。
4. 生成和发送消息到http: //localhost:4000/IHelloWCF。
5. 接受消息,实例化一个HelloWCF对象,分发消息到服务实例的Say方法上。
看一下消息
现在代码写完了,貌似没看到我们HelloWCF例子里哪里使用到了消息。对于开发者,一个WCF应用看起来和感觉都很
像面向对象或者面向组件的应用。在运行时,WCF应用要生成、发送和接受消息,同样也要处理消息。通过修改Say
方法的实现我们能看到WCF接触结构的消息:
public void Say(String input){
Console.WriteLine( "Message received, the body contains: {0}", input);
// Show the contents of the received message显示接受消息内容
Console.WriteLine(
OperationContext.Current.RequestContext.RequestMessage.ToString());
}
修改Say方法后的输出如下:
The HelloWCF receiving application is ready
HelloWCF object created
Message received, the body contains: HelloWCF!
<s:Envelope xmlns:s= "http://schemas.xmlsoap.org/soap/envelope/">
<s:Header>
<To s:mustUnderstand="1"
xmlns="http://schemas.microsoft.com/ws/2005/05/adessing/none">
http://localhost:8000/IHelloWCF
</To>
<Action s:mustUnderstand="1"
xmlns="http://schemas.microsoft.com/ws/2005/05/addressing/none">
http://tempuri.org/IHelloWCF/Say
</Action>
</s:Header>
<s:Body>
<Say xmlns="http://tempuri.org/">
<input>HelloWCF!</input>
</Say>
</s:Body>
</s:Envelope>
Foundation\System.ServiceModel.dll" HelloWCFApp.cs
c:\temp>HelloWCFApp.exe
The HelloWCF receiving application is ready
HelloWCF object created
Message received, the body contains: HelloWCF!
如期望的一样,我们的程序执行步骤如下:
1. 为接受来自http: //localhost:4000/IHelloWCF的消息构建基础结构。
2. 开始在http: //localhost:4000/IHelloWCF上侦听消息。
3. 构建发送到http: //localhost:4000/IHelloWCF消息的基础结构。
4. 生成和发送消息到http: //localhost:4000/IHelloWCF。
5. 接受消息,实例化一个HelloWCF对象,分发消息到服务实例的Say方法上。
看一下消息
现在代码写完了,貌似没看到我们HelloWCF例子里哪里使用到了消息。对于开发者,一个WCF应用看起来和感觉都很
像面向对象或者面向组件的应用。在运行时,WCF应用要生成、发送和接受消息,同样也要处理消息。通过修改Say
方法的实现我们能看到WCF接触结构的消息:
public void Say(String input){
Console.WriteLine( "Message received, the body contains: {0}", input);
// Show the contents of the received message显示接受消息内容
Console.WriteLine(
OperationContext.Current.RequestContext.RequestMessage.ToString());
}
修改Say方法后的输出如下:
The HelloWCF receiving application is ready
HelloWCF object created
Message received, the body contains: HelloWCF!
<s:Envelope xmlns:s= "http://schemas.xmlsoap.org/soap/envelope/">
<s:Header>
<To s:mustUnderstand="1"
xmlns="http://schemas.microsoft.com/ws/2005/05/adessing/none">
http://localhost:8000/IHelloWCF
</To>
<Action s:mustUnderstand="1"
xmlns="http://schemas.microsoft.com/ws/2005/05/addressing/none">
http://tempuri.org/IHelloWCF/Say
</Action>
</s:Header>
<s:Body>
<Say xmlns="http://tempuri.org/">
<input>HelloWCF!</input>
</Say>
</s:Body>
</s:Envelope>
注意到打印的
SOAP消息,消息的Body部分包含我们传递给局部变量的channel 上Say方法的字符串。宏观上讲,我们的
应用程序使用这个字符来构建一个SOAP消息,然后发送这个SOAP消息到我们程序的接受部分。接受部分,换句话说,它
要接受SOAP消息,创建一个HelloWCF实例,提取SOAP Body的内容,调用HelloWCF 实例的Say方法,传递字符串参数。
小变化大影响
WCF基础结构为我们做了大部分消息处理工作,一般的对象模型都不会揭示我们的WCF程序是如何在发送者和接受者之间
传递消息的事实。实际上,从开发者角度来看,我们的程序里展示的代码更像是在使用分布式对象编程而不是消息应用。
通过修改一行代码我们能够很容易地看出HelloWCF程序实际上是一个消息应用,并且我们可以观察一下这个变化带来对
消息组成带来的影响。
如果我们修改代码
BasicHttpBinding binding =
new BasicHttpBinding();
变为如下:
WSHttpBinding binding =
new WSHttpBinding();
我们会看到如下的输入:
The HelloWCF receiving application
is ready
Creating and sending a message to the receiver
HelloWCF object created
Message received, the body contains: HelloWCF!
<s:Envelope xmlns:a= "http://www.w3.org/2005/08/addressing"
xmlns:s="http://www.w3.org/2003/05/soap-envelope">
<s:Header>
<a:Action s:mustUnderstand="1" u:Id="_2"
xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis-200401-
wss-wssecurity-utility-1.0.xsd">
http://tempuri.org/IHelloWCF/Say
</a:Action>
<a:MessageID u:Id="_3"
xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis-
200401-wss-wssecurity-utility-1.0.xsd">
urn:uuid:
</a:MessageID>
<a:ReplyTo u:Id="_4"
xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis-
200401-wss-wssecurity-utility-1.0.xsd">
<a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address>
</a:ReplyTo>
<a:To s:mustUnderstand="1" u:Id="_5" xmlns:u="http://docs.oasis-open.org/wss/2004/01/
oasis-200401-wss-wssecurity-utility-1.0.xsd">
http://localhost:8000/IHelloWCF
</a:To>
<o:Security s:mustUnderstand="1" xmlns:o="http://docs.oasis-open.org/wss/2004/01/oasis-
200401-wss-wssecurity-secext-1.0.xsd">
<u:Timestamp u:Id="uuid--12"
xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis-
200401-wss-wssecurity-utility-1.0.xsd">
<u:Created>2006-08-29T01:57:50.296Z</u:Created>
<u:Expires>2006-08-29T02:02:50.296Z</u:Expires>
</u:Timestamp>
<c:SecurityContextToken u:Id="uuid--6"
xmlns:c="http://schemas.xmlsoap.org/ws/2005/02/sc" xmlns:u="http://docs.oasis-open.org/wss/
2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
<c:Identifier>
urn:uuid:
</c:Identifier>
</c:SecurityContextToken>
<c:DerivedKeyToken
u:Id="uuid--10"
xmlns:c="http://schemas.xmlsoap.org/ws/2005/02/sc"
xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-
wssecurity-utility-1.0.xsd">
<o:SecurityTokenReference>
<o:Reference
ValueType="http://schemas.xmlsoap.org/ws/2005/02/sc/sct"
URI="#uuid--6" />
</o:SecurityTokenReference>
<c:Offset>0</c:Offset>
<c:Length>24</c:Length>
<c:Nonce>A170b1nKz88AuWmWYONX5Q==</c:Nonce>
</c:DerivedKeyToken>
<c:DerivedKeyToken
u:Id="uuid--11"
xmlns:c="http://schemas.xmlsoap.org/ws/2005/02/sc"
xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-
wssecurity-utility-1.0.xsd">
<o:SecurityTokenReference>
<o:Reference
ValueType="http://schemas.xmlsoap.org/ws/2005/02/sc/sct"
URI="#uuid--6" />
</o:SecurityTokenReference>
<c:Nonce>I8M/H2f3vFuGkwZVV1Yw0A==</c:Nonce>
</c:DerivedKeyToken>
<e:ReferenceList xmlns:e="http://www.w3.org/2001/04/xmlenc#">
<e:DataReference URI="#_1" />
<e:DataReference URI="#_6" />
</e:ReferenceList>
<e:EncryptedData Id="_6"
Type="http://www.w3.org/2001/04/xmlenc#Element"
xmlns:e="http://www.w3.org/2001/04/xmlenc#">
<e:EncryptionMethod
Algorithm="http://www.w3.org/2001/04/xmlenc#aes256-cbc" />
<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
<o:SecurityTokenReference>
<o:Reference
ValueType="http://schemas.xmlsoap.org/ws/2005/02/sc/dk"
URI="#uuid--11" />
</o:SecurityTokenReference>
</KeyInfo>
<e:CipherData>
<e:CipherValue>
vQ+AT5gioRS6rRiNhWw2UJmvYYZpA+cc1DgC/K+6Dsd2enF4RUcwOG2
xqfkD/
EZkSFRKDzrJYBz8ItHLZjsva4kqfx3UsEJjYPKbxihl2GFrXdPwTmrHWt35Uw0L2rTh8kU9rtj44NfULS59CJbXE6PC7
Af1qWvnobcPXBqmgm4NA8wwSTuR3IKHPfD/Pg/
3WABob534WD4T1DbRr5tXwNr+yQl2nSWN8C0aaP9+LCKymEK7AbeJXAaGoxdGu/
t6l7Bw1lBsJeSJmsd4otXcLxt976kBEIjTl8/
6SVUd2hmudP2TBGDbCCvgOl4c0vsHmUC1SjXE5vXf6ATkMj6P3o0eMqBiWlG26RWiYBZ3OxnC1fDs60uSvfHtfF8CD0I
LYGHLgnUHz5CFY0rPomT73RCkCfmgFuheCgB9zHZGtWedY6ivNrZe2KPx0ujQ2Mq4pv4bLns2qoykwK03ma7YGiGExGc
ZBfkZ2YAkYmHWXJ0Xx4PJmQRAWIKfUCqcrR6lwyLjl5Agsrt0xHA5WEk3hapscW3HZ8wOgwv0fcHlZ1e3EAm0dZr5Ose
3TAKMXf7FC1tMy5u0763flA6AZk9l7IpAQXcTLYicriH5hzf1416xbTJCtt2rztiItSkYizkiJCUMJLanc6ST5i+GVHz
J5oRCEWgfOTcQpHmri8y1P1+6jYe9ELla8Mj
</e:CipherValue>
</e:CipherData>
</e:EncryptedData>
</o:Security>
</s:Header>
<s:Body u:Id="_0" xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis-
200401-wss-wssecurity-utility-1.0.xsd">
<Say xmlns="http://tempuri.org/">
<input>HelloWCF!</input>
</Say>
</s:Body>
</s:Envelope>
Creating and sending a message to the receiver
HelloWCF object created
Message received, the body contains: HelloWCF!
<s:Envelope xmlns:a= "http://www.w3.org/2005/08/addressing"
xmlns:s="http://www.w3.org/2003/05/soap-envelope">
<s:Header>
<a:Action s:mustUnderstand="1" u:Id="_2"
xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis-200401-
wss-wssecurity-utility-1.0.xsd">
http://tempuri.org/IHelloWCF/Say
</a:Action>
<a:MessageID u:Id="_3"
xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis-
200401-wss-wssecurity-utility-1.0.xsd">
urn:uuid:
</a:MessageID>
<a:ReplyTo u:Id="_4"
xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis-
200401-wss-wssecurity-utility-1.0.xsd">
<a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address>
</a:ReplyTo>
<a:To s:mustUnderstand="1" u:Id="_5" xmlns:u="http://docs.oasis-open.org/wss/2004/01/
oasis-200401-wss-wssecurity-utility-1.0.xsd">
http://localhost:8000/IHelloWCF
</a:To>
<o:Security s:mustUnderstand="1" xmlns:o="http://docs.oasis-open.org/wss/2004/01/oasis-
200401-wss-wssecurity-secext-1.0.xsd">
<u:Timestamp u:Id="uuid--12"
xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis-
200401-wss-wssecurity-utility-1.0.xsd">
<u:Created>2006-08-29T01:57:50.296Z</u:Created>
<u:Expires>2006-08-29T02:02:50.296Z</u:Expires>
</u:Timestamp>
<c:SecurityContextToken u:Id="uuid--6"
xmlns:c="http://schemas.xmlsoap.org/ws/2005/02/sc" xmlns:u="http://docs.oasis-open.org/wss/
2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
<c:Identifier>
urn:uuid:
</c:Identifier>
</c:SecurityContextToken>
<c:DerivedKeyToken
u:Id="uuid--10"
xmlns:c="http://schemas.xmlsoap.org/ws/2005/02/sc"
xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-
wssecurity-utility-1.0.xsd">
<o:SecurityTokenReference>
<o:Reference
ValueType="http://schemas.xmlsoap.org/ws/2005/02/sc/sct"
URI="#uuid--6" />
</o:SecurityTokenReference>
<c:Offset>0</c:Offset>
<c:Length>24</c:Length>
<c:Nonce>A170b1nKz88AuWmWYONX5Q==</c:Nonce>
</c:DerivedKeyToken>
<c:DerivedKeyToken
u:Id="uuid--11"
xmlns:c="http://schemas.xmlsoap.org/ws/2005/02/sc"
xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-
wssecurity-utility-1.0.xsd">
<o:SecurityTokenReference>
<o:Reference
ValueType="http://schemas.xmlsoap.org/ws/2005/02/sc/sct"
URI="#uuid--6" />
</o:SecurityTokenReference>
<c:Nonce>I8M/H2f3vFuGkwZVV1Yw0A==</c:Nonce>
</c:DerivedKeyToken>
<e:ReferenceList xmlns:e="http://www.w3.org/2001/04/xmlenc#">
<e:DataReference URI="#_1" />
<e:DataReference URI="#_6" />
</e:ReferenceList>
<e:EncryptedData Id="_6"
Type="http://www.w3.org/2001/04/xmlenc#Element"
xmlns:e="http://www.w3.org/2001/04/xmlenc#">
<e:EncryptionMethod
Algorithm="http://www.w3.org/2001/04/xmlenc#aes256-cbc" />
<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
<o:SecurityTokenReference>
<o:Reference
ValueType="http://schemas.xmlsoap.org/ws/2005/02/sc/dk"
URI="#uuid--11" />
</o:SecurityTokenReference>
</KeyInfo>
<e:CipherData>
<e:CipherValue>
vQ+AT5gioRS6rRiNhWw2UJmvYYZpA+cc1DgC/K+6Dsd2enF4RUcwOG2
xqfkD/
EZkSFRKDzrJYBz8ItHLZjsva4kqfx3UsEJjYPKbxihl2GFrXdPwTmrHWt35Uw0L2rTh8kU9rtj44NfULS59CJbXE6PC7
Af1qWvnobcPXBqmgm4NA8wwSTuR3IKHPfD/Pg/
3WABob534WD4T1DbRr5tXwNr+yQl2nSWN8C0aaP9+LCKymEK7AbeJXAaGoxdGu/
t6l7Bw1lBsJeSJmsd4otXcLxt976kBEIjTl8/
6SVUd2hmudP2TBGDbCCvgOl4c0vsHmUC1SjXE5vXf6ATkMj6P3o0eMqBiWlG26RWiYBZ3OxnC1fDs60uSvfHtfF8CD0I
LYGHLgnUHz5CFY0rPomT73RCkCfmgFuheCgB9zHZGtWedY6ivNrZe2KPx0ujQ2Mq4pv4bLns2qoykwK03ma7YGiGExGc
ZBfkZ2YAkYmHWXJ0Xx4PJmQRAWIKfUCqcrR6lwyLjl5Agsrt0xHA5WEk3hapscW3HZ8wOgwv0fcHlZ1e3EAm0dZr5Ose
3TAKMXf7FC1tMy5u0763flA6AZk9l7IpAQXcTLYicriH5hzf1416xbTJCtt2rztiItSkYizkiJCUMJLanc6ST5i+GVHz
J5oRCEWgfOTcQpHmri8y1P1+6jYe9ELla8Mj
</e:CipherValue>
</e:CipherData>
</e:EncryptedData>
</o:Security>
</s:Header>
<s:Body u:Id="_0" xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis-
200401-wss-wssecurity-utility-1.0.xsd">
<Say xmlns="http://tempuri.org/">
<input>HelloWCF!</input>
</Say>
</s:Body>
</s:Envelope>
.
正如你看到的,一个简单的修改给我们的消息应用生成的消息结构带来的了巨大的影响。BasicHttpBinding 到WSHttpBinding的转换使得我们的程序从通过HTTP发送简单的SOAP消息到通过HTTP发送遵守WS-*规范和编排的复杂消息。这个影响不单单是一个冗长的消息。因为我们的程序现在可以发送和接受基于WS-Security、
WS-SecureConversation和其它消息规范的复杂消息。
注释:实际上,WCF宏观编程模型去除了“WCF是一个消息应用”的外观,而提供了更多的分布式对象的“感觉”。在我个人看来,这是平台最大的好处,但是同时充满了危险。作为开发人员,我们必须抵抗住WCF是分布式对象平台的诱惑,而接受消息的概念。此外,作为一个应用程序和框架的开发人员,我们必须理解如何改变使用WCF类型的方式来影响我们应用系统处理的消息。
暴露元数据
我们的
Hello WCF程序使用一个相当简单的方法就实现了接收者和发送者之间的兼容性。因为接收者和发送者驻留在同一个AppDo-main里,并且接收者使用的对象对于发送者来说是可见的,我们在发送者代码里简单地重用了地址、绑定和契约。
在发部分消息应用里,这个方法是可行的。绝大多数情况,我们希望发送者和接收者去驻留在不同的机器上的AppDomains里。在这些场景里,接收者显示指定消息需求,发送者遵守这些需求。
WS- MetadataExchange规范规定了发送者和接收者在平台无关时如何交换这些数据信息。在更多的条款里,WS-MetadataExchange规范限定了方便终结点之间进行消息交换的schema和编排。在大部分现实世界的应用系统里(或者至
少
比我们的Hello WCF程序复杂的应用)。有一个暴露信息方式的需求,这个方式就是发送者询问接收者的终结点去提取元数据,并使用这些元数据构建能发送给接收终结点消息的基础结构。
默认情况下,我的
Hello WCF程序不会暴露任何元数据,不会是广泛接受的Web服务描述语言(WSDL)和扩展 Schema 定义 (XSD)。(不要把消息应用的元数据和程序集或者类型元数据混淆,即使一个可以用来创建另外一个。)事实上,WCF
缺省的情况下不会暴露元数据,这些原因都是因为对安全的考虑。元数据暴露的信息包含应用系统的安全需求。以保护秘密的
名义,这个团队选择缺省情况下关闭这个特性。
如果决定暴露系统的元数据,我们可以构建一个暴露元数据的终结点,并且构建元数据终结点的方式和其它终结点非常相似:
使用地址、绑定和契约。但是目前为止你看到的终结点不太一样,就是服务契约已经定义到
WCF的API里了。
构建元数据终结点的第一步是修改
ServiceHost到可以托管元数据的状态。我们可以通过System.ServiceModel. Description.ServiceMetadataBehavior对象增加到ServiceHost行为集合里。行为是WCF基础结构用来改变本地消息处理
的特定信息。下面代码演示了如何增加ServiceMetadataBehavior对象到活动的ServiceHost对象:
// instantiate a ServiceHost, passing the type to instantiate// when the application receives a messageServiceHost svc = new ServiceHost(typeof(HelloWCF), address); // BEGIN NEW METADATA CODE// create a ServiceMetadataBehavior创建服务元数据行为ServiceMetadataBehavior metadata = new ServiceMetadataBehavior();metadata.HttpGetEnabled = true;// add it to the servicehost descriptionsvc.Description.Behaviors.Add(metadata);
下一步就是为元数据终结点定义Binding。元数据绑定的对象模型与其它绑定区别很大,我们通过调用工厂方法上的
System.ServiceModel.Description.MetadataExchangeBindings的类型创建元数据绑定,如下所示(WCF程序的代码
其它部分忽略):
// instantiate a ServiceHost, passing the type to instantiate
// when the application receives a message
ServiceHost svc = new ServiceHost( typeof(HelloWCF));
// BEGIN NEW METADATA CODE
// create a ServiceMetadataBehavior创建服务元数据行为
ServiceMetadataBehavior metadata = new ServiceMetadataBehavior();
// add it to the servicehost description
svc.Description.Behaviors.Add(metadata);
// create a TCP metadata binding创建TCP元数据绑定
Binding mexBinding = MetadataExchangeBindings.CreateMexTcpBinding();
// when the application receives a message
ServiceHost svc = new ServiceHost( typeof(HelloWCF));
// BEGIN NEW METADATA CODE
// create a ServiceMetadataBehavior创建服务元数据行为
ServiceMetadataBehavior metadata = new ServiceMetadataBehavior();
// add it to the servicehost description
svc.Description.Behaviors.Add(metadata);
// create a TCP metadata binding创建TCP元数据绑定
Binding mexBinding = MetadataExchangeBindings.CreateMexTcpBinding();
由于
ASMX(ASP.NET Web Service)的影响,你也许会认为元数据只能通过HTTP传输呈现。事实上,元数据可以通过多种传输协议传递,并且WS-MetadataExchange说明了这个灵活性。在我们的例子里,我们调用CreateMexTcpBinding方法,它返回了一个继承自Binding类型的TCP 传输绑定。因为我们使用的是TCP传输,所以我们必须全包元数据地址使用了TCP
地址格式,如下所示:
// instantiate a ServiceHost, passing the type to instantiate
// when the application receives a message
ServiceHost svc = new ServiceHost( typeof(HelloWCF));
// BEGIN NEW METADATA CODE
// create a ServiceMetadataBehavior
ServiceMetadataBehavior metadata = new ServiceMetadataBehavior();
// add it to the servicehost description
svc.Description.Behaviors.Add(metadata);
// create a TCP metadata binding
Binding mexBinding = MetadataExchangeBindings.CreateMexTcpBinding();
// create an address to listen on WS-Metadata exchange traffic
//创建元数据交换侦听地址
Uri mexAddress = new Uri( "net.tcp://localhost:5000/IHelloWCF/Mex");
// when the application receives a message
ServiceHost svc = new ServiceHost( typeof(HelloWCF));
// BEGIN NEW METADATA CODE
// create a ServiceMetadataBehavior
ServiceMetadataBehavior metadata = new ServiceMetadataBehavior();
// add it to the servicehost description
svc.Description.Behaviors.Add(metadata);
// create a TCP metadata binding
Binding mexBinding = MetadataExchangeBindings.CreateMexTcpBinding();
// create an address to listen on WS-Metadata exchange traffic
//创建元数据交换侦听地址
Uri mexAddress = new Uri( "net.tcp://localhost:5000/IHelloWCF/Mex");
既然我们定义了元数据终结点需要的地址和绑定,我们要添加终结点到
ServiceHost上,方式很像我们定义的第一个消息终结点。当添加元数据终结点时,我们要使用WCF API定义的名为System.ServiceModel.Description.IMetadataExchange的服务契约。下面代码演示了如何添加一个元数据终结点到ServiceHost上,使用适当的地址、绑定和契约。
// instantiate a ServiceHost, passing the type to instantiate
// when the application receives a message
ServiceHost svc = new ServiceHost( typeof(HelloWCF));
// BEGIN NEW METADATA CODE
// create a ServiceMetadataBehavior
ServiceMetadataBehavior metadata = new ServiceMetadataBehavior();
// add it to the servicehost description
svc.Description.Behaviors.Add(metadata);
// create a TCP metadata binding
Binding mexBinding = MetadataExchangeBindings.CreateMexTcpBinding();
// create an address to listen on WS-Metadata exchange traffic
Uri mexAddress = new Uri( "net.tcp://localhost:5000/IHelloWCF/Mex");
// add the metadata endpoint添加元数据终结点
svc.AddServiceEndpoint(typeof(IMetadataExchange),
mexBinding,
mexAddress);
// END METADATA CODE
// when the application receives a message
ServiceHost svc = new ServiceHost( typeof(HelloWCF));
// BEGIN NEW METADATA CODE
// create a ServiceMetadataBehavior
ServiceMetadataBehavior metadata = new ServiceMetadataBehavior();
// add it to the servicehost description
svc.Description.Behaviors.Add(metadata);
// create a TCP metadata binding
Binding mexBinding = MetadataExchangeBindings.CreateMexTcpBinding();
// create an address to listen on WS-Metadata exchange traffic
Uri mexAddress = new Uri( "net.tcp://localhost:5000/IHelloWCF/Mex");
// add the metadata endpoint添加元数据终结点
svc.AddServiceEndpoint(typeof(IMetadataExchange),
mexBinding,
mexAddress);
// END METADATA CODE
如果我们构建和运行
Hello WCF程序,就会看到程序确实在2个不同的地址上侦听。一个地址是用作服务元数据地址,另外
一个地址是为了IHelloWCF.Say功能。让我们看一下如何从元数据终结点提取元数据并使用它来构建程序里的发送端基础结构。
使用元数据
Microsoft .NET Framework SDK安装了一个功能强大的工具名字是svcutil.exe,它的一个功能就是询问一个运行的消息
应用并基于获得的信息生成代理。从内部来说,svcutil.exe使用的是WS-MetadataExchange协议,像与ASMX一起普及的WSDL里的“get”语义一样。因为我们的接收程序暴露了一个元数据终结点,我们可以把svcutil.exe指向这个运行的终结点,svcutil.exe会自动生成一个代理类型和与服务终结点兼容的配置信息,这些信息都是参考元数据生成。当使用这种方式的时候,svcutil.exe 依照WS-MetadataExchange的方式发送消息给接收程序,并转化这些消息为.NET Framework的类型以
方便发送消息程序的开发。
使用Svcutil.exe生成代理
在你运行
svcutil.exe以前,检查一下HelloWCFApp.exe正常运行并侦听请求消息。下一步就是打开一个新的Windows SDK命令行窗口,输入下面的命令:
C:\temp>svcutil /target:code net.tcp:
//localhost:5000/IHelloWCF/Mex
Svcutil.exe会创建2个文件:HelloWCFProxy.cs 和output.config。如果你检查一下HelloWCFProxy.cs文件,
你就会看到svcutil.exe产生了一个包含IHelloWCF、IHelloWCFChannel,和HelloWCFClient的代码文件。
注释:在svcutil.exe生成的所有类型里,HelloWCFClient类型是使用最频繁的。我的观点是,名称后面加上Client后缀毫无疑问将会在开发人员里带来误解。毋容置疑,Client言外之意就是Client 和Server。HelloWCFClient类型帮助我们构建消息基础结构,不似乎传统的客户端/服务器结构。一定要记住,即使它使用Client后缀,我们仍然是构建消息应用。
合起来看,这些类型定义就是帮助我们创建于接受程序兼容的发送代码。注意
HelloWCF.cs文件里没有任何接受程序的地址,
也没有与HelloWCF.cs源文件匹配的绑定。这些信息都存贮在svcutil.exe生成的另外一个文件里(output.config)。WCF提供了丰富的配置选项允许我们来给发送和接受程序通过XML配置文件定制不同的行为。为了演示如何利用svcutil创建的数据,
让我们创建另外一个发送消息控制台程序。我们把它命名为HelloWCFSender。这里我们要重命名一下配置文件以便新的发送程序可以读取这个配置文件(修改为HelloWCFSender.exe.config)。
使用Svcutil.exe生成的类型为HelloWCFSender编写代码
总之,svcutil.exe为我们的发送程序生成了大部分代码和配置信息。创建发送程序与HelloWCF.exe非常相似。
using System;
using System.ServiceModel;
sealed class HelloWCFSender {
static void Main(){
// wait for the receiver to start
Console.WriteLine( "Press ENTER when the Receiver is ready");
Console.ReadLine();
// print to the console that we are sending a message
Console.WriteLine( "Sending a message to the Receiver");
// create the HelloWCFClient type created by svcutil
HelloWCFClient proxy = new HelloWCFClient();
// invoke the Say method
proxy.Say( "Hi there from a new Sender");
proxy.Close();
// print to the console that we have sent a message
Console.WriteLine( "Finished sending a message to the Receiver");
}
}
using System.ServiceModel;
sealed class HelloWCFSender {
static void Main(){
// wait for the receiver to start
Console.WriteLine( "Press ENTER when the Receiver is ready");
Console.ReadLine();
// print to the console that we are sending a message
Console.WriteLine( "Sending a message to the Receiver");
// create the HelloWCFClient type created by svcutil
HelloWCFClient proxy = new HelloWCFClient();
// invoke the Say method
proxy.Say( "Hi there from a new Sender");
proxy.Close();
// print to the console that we have sent a message
Console.WriteLine( "Finished sending a message to the Receiver");
}
}
注意到我们只实例化了HelloWCFClient类型并调用
Say方法。实际复杂的工作都由svcutil.exe生产的类型和WCF配置基础结构完成了。在我们写完这些代码以后,我们可以使用下面的命令编译为一个程序集:
C:\temp>csc /r: "C:\WINDOWS\Microsoft.Net\v3.0\Windows CommunicationFoundation\System.ServiceModel.dll" HelloWCFProxy.cs HelloWCFSender.cs
接下来我们启动接受程序(HelloWCFApp.exe),然后启动发送程序(HelloWCFSender.exe),我们就会在发送端看到下面这样的输出消息:
C:\temp>HelloWCFSender.exe
Press ENTER when the Receiver is ready
Sending a message to the Receiver
Finished sending a message to the Receiver
Press ENTER when the Receiver is ready
Sending a message to the Receiver
Finished sending a message to the Receiver
概括地说,我们程序的输出结果证实了发送部分和以前一样工作,而没有重用我们构建接收者部分使用的对象。我们可以检查接受消息的应用去验证接收者确实收到了一个新的消息。
既然我们有了2
个功能完善的WCF
应用,那我们就来从总体上看看WCF
的架构。
本文转自 frankxulei 51CTO博客,原文链接:
http://blog.51cto.com/frankxulei/318606
,如需转载请自行联系原作者