本系列的第一部分关注WCF的行为(behavior,在这个语境中是个术语,不晓得要不要直译出来)。针对不同的情形,共有对应的4种behavior:service, endpoint, contract 和 operation behaviors。这些behavior接口能作为几乎所有其他扩展功能的入口点,在实现它们的过程中我们能方便地取得后者(其他扩展功能)的引用。
描述和运行时(Description vs. Runtime)
WCF behavior是服务描述的一部分,描述当服务开始运行时——意即宿主open时——会发生什么。当宿主实例被创建,终结点被添加,监听器尚未启动时,程序能够修改服务描述,来定义一旦服务开始运行后的behave。如果没有定义behave,那么宿主启动毫无意义
当宿主open之后(IIS/WAS宿主是在第一个消息到达可用服务时),它将会初始化所有的监听器、路由器、过滤器、通道、拦截器等(listeners, dispatchers, filters, channels, hooks, etc.),这些是将消息正确发送到指定操作的必要条件。大多数扩展功能是服务运行时的一部分,由于运行时要在宿主open之后才初始,因此用户可能需要运行时已经准备就绪的通知。
Behaviors
WCF behaviors声明为四个接口(命名空间是System.ServiceModel.Description): IServiceBehavior, IEndpointBehavior, IContractBehavior 和IOperationBehavior. 它们的结构相同:
public interface I[Service/Endpoint/Contract/Operation]Behavior {
void Validate(DescriptionObject);
void AddBindingParameters(DescriptionObject, BindingParameterCollection);
void ApplyDispatchBehavior(DescriptionObject, RuntimeObject);
void ApplyClientBehavior(DescriptionObject, RuntimeObject); // not on IServiceBehavior
}
接口中的四个方法被调用的顺序如下:
- Validate: 如果验证逻辑发现有某些问题(原文描述 if the validation logic finds something in the service / endpoint / contract / operation description which it deems invalid.),它将阻止宿主或客户端打开。
- AddBindingParameters: 向BindingParameterCollection添加元素, 该集合在binding元素创建listeners / factories时使用(at runtime)。在behaviors和bindings之间添加相关对象的做法很有用。对服务behaviors来说,该方法对每个终结点都调用一次,BindingParameterCollection在为每个终结点创建监听器的时候使用。
- Apply[Client/Dispatch]Behavior: 在这两个方法中我们能够获取并修改运行时对象。这两个方法最常被使用,其余两个往往为空实现。在之后的文章中我会针对每一个behavior的这两个方法提供实际项目中的示例代码。
对于这四个接口中的相同方法,调用顺序为service-contract-endpoint-operation。
给WCF添加behaviors
behaviors能以三种方式添加:
- Code: service / endpoint / contract / operation对象的description有一个behavior的集合的引用,通过它你可以方便地添加任意一个behavior。
- Configuration: 对service 和endpoint behaviors来说,可以通过system.serviceModel/behaviors 节点添加,然后通过service behaviors 或endpoint behaviors 指定使用哪个behavior.
- Attributes: 除了endpoint behaviors,我们可以使用attributes来给其余三个对象添加behaviors。你可以创建一个attribute并实现上述四个behavior接口中的一个,然后将之应用到指定对象上,那么该对象的description也就有了相应的behavior。
下面的代码展示了一个简单的服务,利用attribute和code(endpoint)来添加behavior。这段代码展示了behaviors和其中方法的调用顺序,另结果显示ApplyClientBehavior并未被调用,暂且搁下。
1
.
using
System;
2
.
using
System.Collections.ObjectModel;
3
.
using
System.Reflection;
4
.
using
System.ServiceModel;
5
.
using
System.ServiceModel.Channels;
6
.
using
System.ServiceModel.Description;
7
.
using
System.ServiceModel.Dispatcher;
8
.
9
.
namespace
Behaviors
10
.{
11
.
class
MyServiceBehaviorAttribute : Attribute, IServiceBehavior
12
. {
13
.
public
void
AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection
<
ServiceEndpoint
>
endpoints, BindingParameterCollection bindingParameters)
14
. {
15
. Console.WriteLine(
"
Inside {0}.{1}
"
,
this
.GetType().Name, MethodBase.GetCurrentMethod().Name);
16
. }
17
.
18
.
public
void
ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
19
. {
20
. Console.WriteLine(
"
Inside {0}.{1}
"
,
this
.GetType().Name, MethodBase.GetCurrentMethod().Name);
21
. }
22
.
23
.
public
void
Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
24
. {
25
. Console.WriteLine(
"
Inside {0}.{1}
"
,
this
.GetType().Name, MethodBase.GetCurrentMethod().Name);
26
. }
27
. }
28
.
29
.
class
MyEndpointBehavior : IEndpointBehavior
30
. {
31
.
public
void
AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
32
. {
33
. Console.WriteLine(
"
Inside {0}.{1}, endpoint {2}
"
,
this
.GetType().Name, MethodBase.GetCurrentMethod().Name, endpoint.Name);
34
. }
35
.
36
.
public
void
ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
37
. {
38
. Console.WriteLine(
"
Inside {0}.{1}, endpoint {2}
"
,
this
.GetType().Name, MethodBase.GetCurrentMethod().Name, endpoint.Name);
39
. }
40
.
41
.
public
void
ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
42
. {
43
. Console.WriteLine(
"
Inside {0}.{1}, endpoint {2}
"
,
this
.GetType().Name, MethodBase.GetCurrentMethod().Name, endpoint.Name);
44
. }
45
.
46
.
public
void
Validate(ServiceEndpoint endpoint)
47
. {
48
. Console.WriteLine(
"
Inside {0}.{1}, endpoint {2}
"
,
this
.GetType().Name, MethodBase.GetCurrentMethod().Name, endpoint.Name);
49
. }
50
. }
51
.
52
.
class
MyContractBehaviorAttribute : Attribute, IContractBehavior
53
. {
54
.
public
void
AddBindingParameters(ContractDescription contractDescription, ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
55
. {
56
. Console.WriteLine(
"
Inside {0}.{1}, contract {2}
"
,
this
.GetType().Name, MethodBase.GetCurrentMethod().Name, contractDescription.ContractType.Name);
57
. }
58
.
59
.
public
void
ApplyClientBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, ClientRuntime clientRuntime)
60
. {
61
. Console.WriteLine(
"
Inside {0}.{1}, contract {2}
"
,
this
.GetType().Name, MethodBase.GetCurrentMethod().Name, contractDescription.ContractType.Name);
62
. }
63
.
64
.
public
void
ApplyDispatchBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, DispatchRuntime dispatchRuntime)
65
. {
66
. Console.WriteLine(
"
Inside {0}.{1}, contract {2}
"
,
this
.GetType().Name, MethodBase.GetCurrentMethod().Name, contractDescription.ContractType.Name);
67
. }
68
.
69
.
public
void
Validate(ContractDescription contractDescription, ServiceEndpoint endpoint)
70
. {
71
. Console.WriteLine(
"
Inside {0}.{1}, contract {2}
"
,
this
.GetType().Name, MethodBase.GetCurrentMethod().Name, contractDescription.ContractType.Name);
72
. }
73
. }
74
.
75
.
class
MyOperationBehaviorAttribute : Attribute, IOperationBehavior
76
. {
77
.
public
void
AddBindingParameters(OperationDescription operationDescription, BindingParameterCollection bindingParameters)
78
. {
79
. Console.WriteLine(
"
Inside {0}.{1}, operation {2}
"
,
this
.GetType().Name, MethodBase.GetCurrentMethod().Name, operationDescription.Name);
80
. }
81
.
82
.
public
void
ApplyClientBehavior(OperationDescription operationDescription, ClientOperation clientOperation)
83
. {
84
. Console.WriteLine(
"
Inside {0}.{1}, operation {2}
"
,
this
.GetType().Name, MethodBase.GetCurrentMethod().Name, operationDescription.Name);
85
. }
86
.
87
.
public
void
ApplyDispatchBehavior(OperationDescription operationDescription, DispatchOperation dispatchOperation)
88
. {
89
. Console.WriteLine(
"
Inside {0}.{1}, operation {2}
"
,
this
.GetType().Name, MethodBase.GetCurrentMethod().Name, operationDescription.Name);
90
. }
91
.
92
.
public
void
Validate(OperationDescription operationDescription)
93
. {
94
. Console.WriteLine(
"
Inside {0}.{1}, operation {2}
"
,
this
.GetType().Name, MethodBase.GetCurrentMethod().Name, operationDescription.Name);
95
. }
96
. }
97
.
98
. [ServiceContract]
99
. [MyContractBehavior]
100
.
public
interface
ITest
101
. {
102
. [OperationContract]
103
. [MyOperationBehavior]
104
.
int
Add(
int
x,
int
y);
105
. [OperationContract]
106
.
int
Multiply(
int
x,
int
y);
107
. }
108
.
109
. [ServiceContract]
110
. [MyContractBehavior]
111
.
public
interface
ITest2
112
. {
113
. [OperationContract]
114
. [MyOperationBehavior]
115
.
string
Echo(
string
text);
116
. }
117
.
118
. [MyServiceBehavior]
119
.
public
class
Service : ITest, ITest2
120
. {
121
.
public
int
Add(
int
x,
int
y) {
return
x
+
y; }
122
.
public
int
Multiply(
int
x,
int
y) {
return
x
*
y; }
123
.
public
string
Echo(
string
text) {
return
text; }
124
. }
125
.
126
.
class
Program
127
. {
128
.
static
void
Main(
string
[] args)
129
. {
130
.
string
baseAddress
=
"
http://
"
+
Environment.MachineName
+
"
:8000/Service
"
;
131
. ServiceHost host
=
new
ServiceHost(
typeof
(Service),
new
Uri(baseAddress));
132
. ServiceEndpoint ep1
=
host.AddServiceEndpoint(
typeof
(ITest),
new
BasicHttpBinding(),
"
basic
"
);
133
. ep1.Name
=
"
Endpoint1-BasicHttp
"
;
134
. ep1.Behaviors.Add(
new
MyEndpointBehavior());
135
. ServiceEndpoint ep2
=
host.AddServiceEndpoint(
typeof
(ITest2),
new
WSHttpBinding(),
"
ws
"
);
136
. ep2.Name
=
"
Endpoint2-WSHttp
"
;
137
. ep2.Behaviors.Add(
new
MyEndpointBehavior());
138
. Console.WriteLine(
"
Opening the host...
"
);
139
. host.Open();
140
. Console.WriteLine(
"
Host opened
"
);
141
. }
142
. }
143
.}
到底哪种添加behaviors的方式好?这没有标准答案,对不同的方案,我们可以采取不同的方式。比如说避免重新编译,那么configuration的方式是最好不过;在声明性编程上采用attribute;更改几率不大的采用code方式。(个人认为configuration是较普遍的方式,但如果要根据用户选择经常更换behaviors等类似情况则attribute方式更好。)
Coming up(不知何意)
对特定behavior接口的分析,我会选取论坛中碰到一些问题以展示它们在实际项目中的应用。
[本篇代码下载]