真实世界:使用WCF扩展记录服务调用时间
WCF 可擴(kuò)展性
WCF 提供了許多擴(kuò)展點(diǎn)供開(kāi)發(fā)人員自定義運(yùn)行時(shí)行為。 WCF 在 Channel Layer 之上還提供了一個(gè)高級(jí)運(yùn)行時(shí),主要是針對(duì)應(yīng)用程序開(kāi)發(fā)人員。在 WCF 文檔中,它常被稱(chēng)為服務(wù)模型層(Service Model Layer)。該高級(jí)運(yùn)行時(shí)主要由一個(gè)稱(chēng)作 Dispatcher(在 ServiceHost 的 Context 中)的組件和一個(gè)稱(chēng)作 Proxy(在客戶(hù)端的 Context 中)的組件組成。
(圖片引自 MSDN Magazine?:?Extending WCF with Custom Behaviors)
每個(gè)擴(kuò)展點(diǎn)都使用接口定義來(lái)擴(kuò)展。
| Parameter Inspection | IParameterInspector | Called before and after invocation to inspect and modify parameter values. |
| Message Formatting | IDispatchMessageFormatter IClientFormatter | Called to perform serialization and deserialization. |
| Message Inspection | IDispatchMessageInspector IClientMessageInspector | Called before send or after receive to inspect and replace message contents. |
| Operation Selection | IDispatchOperationSelector IClientOperationSelector | Called to select the operation to invoke for the given message. |
| Operation Invoker | IOperationInvoker | Called to invoke the operation. |
Behavior 是一種特殊類(lèi)型的類(lèi),它在 ServiceHost/ChannelFactory 初始化過(guò)程中擴(kuò)展運(yùn)行時(shí)行為。WCF 有四種類(lèi)型的行為:服務(wù)行為、終結(jié)點(diǎn)行為、契約行為和操作行為。
| ? | ? | Service | Endpoint | Contract | Operation |
| Service | IServiceBehavior | ? | ? | ? | ? |
| Endpoint | IEndpointBehavior | ? | ? | ? | ? |
| Contract | IContractBehavior | ? | ? | ? | ? |
| Operation | IOperationBehavior | ? | ? | ? | ? |
每種行為類(lèi)型也是通過(guò)不同的接口定義來(lái)擴(kuò)展,它們都共用一組相同的方法。一個(gè)例外是,IServiceBehavior 沒(méi)有 ApplyClientBehavior 方法,因?yàn)榉?wù)行為不能用于客戶(hù)端。
| Validate | Called just before the runtime is built—allows you to perform custom validation on the service description. |
| AddBindingParameters | Called in the first step of building the runtime, before the underlying channel is constructed, allows you to add parameters to influence the underlying channel stack. |
| ApplyClientBehavior | Allows behavior to inject proxy (client) extensions. Note that this method is not present on IServiceBehavior. |
| ApplyDispatchBehavior | Allows behavior to inject dispatcher extensions. |
WCF擴(kuò)展點(diǎn)
(圖片引自 lovecindywang = lovecherry 博客)
案例:使用WCF擴(kuò)展記錄服務(wù)調(diào)用時(shí)間
服務(wù)定義:
1 [ServiceContract] 2 public interface ICalculatorService 3 { 4 [OperationContract] 5 int Add(int a, int b); 6 }服務(wù)實(shí)現(xiàn):
1 public class CalculatorService : ICalculatorService 2 { 3 public int Add(int a, int b) 4 { 5 return a + b; 6 } 7 }配置文件:
1 <system.serviceModel> 2 <services> 3 <service name="WcfExtensibilityTestServiceConsole.CalculatorService"> 4 <endpoint address="" binding="netTcpBinding" bindingConfiguration="" 5 contract="WcfExtensibilityTestServiceConsole.ICalculatorService" 6 name="myCalculatorServiceEndpoint"/> 7 <endpoint address="mex" binding="mexTcpBinding" contract="IMetadataExchange"/> 8 <host> 9 <baseAddresses> 10 <add baseAddress="net.tcp://localhost:12345/CalculatorService"/> 11 </baseAddresses> 12 </host> 13 </service> 14 </services> 15 <behaviors> 16 <serviceBehaviors> 17 <behavior> 18 <serviceMetadata/> 19 <serviceDebug includeExceptionDetailInFaults="true"/> 20 </behavior> 21 </serviceBehaviors> 22 </behaviors> 23 </system.serviceModel>問(wèn)題描述
現(xiàn)在需要記錄每次服務(wù)調(diào)用執(zhí)行所需的時(shí)間,比如可以將測(cè)量的方法執(zhí)行時(shí)間傳遞給 PerformanceCounter 用于性能計(jì)數(shù),或者直接寫(xiě)入到日志中等。
方式一:直接在方法內(nèi)測(cè)量
1 public class CalculatorService : ICalculatorService 2 { 3 public int Add(int a, int b) 4 { 5 Stopwatch watch = Stopwatch.StartNew(); 6 7 int result = a + b; 8 Thread.Sleep(TimeSpan.FromMilliseconds(123)); // waste time here 9 10 Debug.WriteLine(string.Format("Method [{0}] execution cost [{1}] milliseconds.", 11 "Add", watch.ElapsedMilliseconds)); 12 13 return result; 14 } 15 }這種方法淺顯易懂,但如果服務(wù)所提供的功能過(guò)多,會(huì)導(dǎo)致大量冗余代碼。
方式二:形成測(cè)量類(lèi)簡(jiǎn)化代碼
1 public class Measure : IDisposable 2 { 3 private Stopwatch _watch = null; 4 5 protected Measure(string methodName) 6 { 7 MethodName = methodName; 8 _watch = new Stopwatch(); 9 } 10 11 public string MethodName { get; private set; } 12 13 public void Start() 14 { 15 _watch.Start(); 16 } 17 18 public void Stop() 19 { 20 _watch.Stop(); 21 22 Debug.WriteLine(string.Format("Measure method [{0}] execution cost [{1}] milliseconds.", 23 MethodName, _watch.ElapsedMilliseconds)); 24 } 25 26 public static Measure StartNew(string methodName) 27 { 28 Measure m = new Measure(methodName); 29 m.Start(); 30 return m; 31 } 32 33 public void Dispose() 34 { 35 Stop(); 36 _watch = null; 37 } 38 } View Code 1 public class CalculatorService : ICalculatorService 2 { 3 public int Add(int a, int b) 4 { 5 using (var measure = Measure.StartNew("Add")) 6 { 7 Thread.Sleep(TimeSpan.FromMilliseconds(123)); // waste time here 8 return a + b; 9 } 10 } 11 }此種方式簡(jiǎn)化了代碼,但仍然需要在各自方法內(nèi)實(shí)現(xiàn),并需提供方法名作為參數(shù)。
使用?Message Inspection 來(lái)解決問(wèn)題
我們定義類(lèi)?IncomingMessageLoggerInspector 來(lái)實(shí)現(xiàn)?IDispatchMessageInspector 接口。
1 #region IDispatchMessageInspector Members 2 3 public object AfterReceiveRequest( 4 ref Message request, 5 IClientChannel channel, 6 InstanceContext instanceContext) 7 { 8 var context = OperationContext.Current; 9 if (context == null) return null; 10 11 var operationName = ParseOperationName(context.IncomingMessageHeaders.Action); 12 13 return MarkStartOfOperation( 14 context.EndpointDispatcher.ContractName, operationName, 15 context.SessionId); 16 } 17 18 public void BeforeSendReply(ref Message reply, object correlationState) 19 { 20 var context = OperationContext.Current; 21 if (context == null) return; 22 23 var operationName = ParseOperationName(context.IncomingMessageHeaders.Action); 24 25 MarkEndOfOperation( 26 context.EndpointDispatcher.ContractName, operationName, 27 context.SessionId, correlationState); 28 } 29 30 #endregion通過(guò)服務(wù)的當(dāng)前上下文實(shí)例,我們可以獲取到服務(wù)被調(diào)用的契約名稱(chēng) ContractName,并且可以在 IncomingMessageHeaders 總解析出被調(diào)用的 OperationName。
我們?cè)诜椒?MarkStartOfOperation 中啟動(dòng) Stopwatch 開(kāi)始測(cè)量執(zhí)行時(shí)間,在方法執(zhí)行完畢后服務(wù)模型會(huì)調(diào)用 BeforeSendReply 并將 Stopwatch 實(shí)例引用傳遞至 correlationState,此時(shí)我們可以在方法 MarkEndOfOperation 中解決時(shí)間測(cè)量,并打印日志。
1 #region Private Methods 2 3 private static string ParseOperationName(string action) 4 { 5 if (string.IsNullOrEmpty(action)) return action; 6 7 string actionName = action; 8 9 int index = action.LastIndexOf('/'); 10 if (index >= 0) 11 { 12 actionName = action.Substring(index + 1); 13 } 14 15 return actionName; 16 } 17 18 private static object MarkStartOfOperation( 19 string inspectorType, string operationName, string sessionId) 20 { 21 var message = string.Format(CultureInfo.InvariantCulture, 22 "Operation [{0}] was called at [{1}] on [{2}] in thread [{3}].", 23 operationName, inspectorType, 24 DateTime.Now.ToString(@"yyyy-MM-dd HH:mm:ss.ffffff", CultureInfo.InvariantCulture), 25 Thread.CurrentThread.ManagedThreadId); 26 27 Debug.WriteLine(message); 28 29 return Stopwatch.StartNew(); 30 } 31 32 private static void MarkEndOfOperation( 33 string inspectorType, string operationName, 34 string sessionId, object correlationState) 35 { 36 var watch = (Stopwatch)correlationState; 37 watch.Stop(); 38 39 var message = string.Format(CultureInfo.InvariantCulture, 40 "Operation [{0}] returned after [{1}] milliseconds at [{2}] on [{3}] in thread [{4}].", 41 operationName, watch.ElapsedMilliseconds, inspectorType, 42 DateTime.Now.ToString(@"yyyy-MM-dd HH:mm:ss.ffffff", CultureInfo.InvariantCulture), 43 Thread.CurrentThread.ManagedThreadId); 44 45 Debug.WriteLine(message); 46 } 47 48 #endregion此時(shí),我們需要定義 EndpointBehavior 來(lái)講 Inspector 設(shè)置到?DispatchRuntime 中。
1 public class IncomingMessageLoggerEndpointBehavior : IEndpointBehavior 2 { 3 #region IEndpointBehavior Members 4 5 public void AddBindingParameters( 6 ServiceEndpoint endpoint, 7 BindingParameterCollection bindingParameters) 8 { 9 } 10 11 public void ApplyClientBehavior( 12 ServiceEndpoint endpoint, 13 ClientRuntime clientRuntime) 14 { 15 } 16 17 public void ApplyDispatchBehavior( 18 ServiceEndpoint endpoint, 19 EndpointDispatcher endpointDispatcher) 20 { 21 if (endpointDispatcher != null) 22 { 23 endpointDispatcher.DispatchRuntime.MessageInspectors.Add( 24 new IncomingMessageLoggerInspector()); 25 } 26 } 27 28 public void Validate(ServiceEndpoint endpoint) 29 { 30 } 31 32 #endregion 33 }然后,我們?cè)?ServiceHost 實(shí)例化后,未Open前,將?IncomingMessageLoggerEndpointBehavior 添加至 Endpoint 的 Behaviors 中。
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 ServiceHost host = new ServiceHost(typeof(CalculatorService)); 6 7 foreach (var endpoint in host.Description.Endpoints) 8 { 9 endpoint.Behaviors.Add(new IncomingMessageLoggerEndpointBehavior()); 10 } 11 12 host.Opened += new EventHandler(delegate(object obj, EventArgs e) 13 { 14 Debug.WriteLine(typeof(CalculatorService).Name + " 服務(wù)已經(jīng)啟動(dòng)!"); 15 }); 16 17 host.Open(); 18 19 Console.ReadKey(); 20 } 21 }使用 WcfTestClient.exe 調(diào)用服務(wù),執(zhí)行 2 + 3 查看結(jié)果,
在 Debug 輸出中可以看到,
使用配置文件定制
使用配置文件定制擴(kuò)展的優(yōu)點(diǎn)就是可以按需添加和刪除擴(kuò)展,而無(wú)需改動(dòng)代碼。比如當(dāng)發(fā)現(xiàn)系統(tǒng)有性能問(wèn)題時(shí),添加該擴(kuò)展來(lái)查看具體哪個(gè)方法執(zhí)行速度慢。
需要定義類(lèi)來(lái)實(shí)現(xiàn)?BehaviorExtensionElement 抽象類(lèi)。
1 public class IncomingMessageLoggerEndpointBehaviorExtension : BehaviorExtensionElement 2 { 3 public override Type BehaviorType 4 { 5 get { return typeof(IncomingMessageLoggerEndpointBehavior); } 6 } 7 8 protected override object CreateBehavior() 9 { 10 return new IncomingMessageLoggerEndpointBehavior(); 11 } 12 }在配置文件中添加擴(kuò)展項(xiàng),
1 <extensions> 2 <behaviorExtensions> 3 <add name="incomingMessageLogger" 4 type="WcfExtensibilityTestServiceConsole.Extensions.IncomingMessageLoggerEndpointBehaviorExtension, WcfExtensibilityTestServiceConsole"/> 5 </behaviorExtensions> 6 </extensions>在終結(jié)點(diǎn)行為中添加該擴(kuò)展定義,
1 <behaviors> 2 <serviceBehaviors> 3 <behavior> 4 <serviceMetadata/> 5 <serviceDebug includeExceptionDetailInFaults="true"/> 6 </behavior> 7 </serviceBehaviors> 8 <endpointBehaviors> 9 <behavior> 10 <incomingMessageLogger/> 11 </behavior> 12 </endpointBehaviors> 13 </behaviors>使用 ServiceBehavior 擴(kuò)展
同理,如果服務(wù)實(shí)現(xiàn)了多個(gè) Endpoint,則想在所有 Endpoint 上添加該擴(kuò)展,除了可以逐個(gè)添加或者使用?behaviorConfiguration 來(lái)配置。
另一個(gè)方法是可以借助 IServiceBehavior 的擴(kuò)展實(shí)現(xiàn)。
1 public class IncomingMessageLoggerServiceBehavior : IServiceBehavior 2 { 3 #region IServiceBehavior Members 4 5 public void AddBindingParameters( 6 ServiceDescription serviceDescription, 7 ServiceHostBase serviceHostBase, 8 Collection<ServiceEndpoint> endpoints, 9 BindingParameterCollection bindingParameters) 10 { 11 } 12 13 public void ApplyDispatchBehavior( 14 ServiceDescription serviceDescription, 15 ServiceHostBase serviceHostBase) 16 { 17 if (serviceHostBase != null) 18 { 19 foreach (ChannelDispatcher channelDispatcher in serviceHostBase.ChannelDispatchers) 20 { 21 foreach (EndpointDispatcher endpointDispatcher in channelDispatcher.Endpoints) 22 { 23 endpointDispatcher.DispatchRuntime.MessageInspectors.Add( 24 new IncomingMessageLoggerInspector()); 25 } 26 } 27 } 28 } 29 30 public void Validate( 31 ServiceDescription serviceDescription, 32 ServiceHostBase serviceHostBase) 33 { 34 } 35 36 #endregion 37 }原理相同,僅是遍歷通道分配器中所有的終結(jié)點(diǎn),逐一添加 Inspector。
同理,如果需要在配置文件中使用,也需要實(shí)現(xiàn)一個(gè)?BehaviorExtensionElement 類(lèi)。
1 public class IncomingMessageLoggerServiceBehaviorExtension : BehaviorExtensionElement 2 { 3 public override Type BehaviorType 4 { 5 get { return typeof(IncomingMessageLoggerServiceBehavior); } 6 } 7 8 protected override object CreateBehavior() 9 { 10 return new IncomingMessageLoggerServiceBehavior(); 11 } 12 }此時(shí)的配置文件描述如下:
1 <behaviors> 2 <serviceBehaviors> 3 <behavior> 4 <serviceMetadata/> 5 <serviceDebug includeExceptionDetailInFaults="true"/> 6 <incomingMessageLogger/> 7 </behavior> 8 </serviceBehaviors> 9 </behaviors> 10 <extensions> 11 <behaviorExtensions> 12 <add name="incomingMessageLogger" 13 type="WcfExtensibilityTestServiceConsole.Extensions.IncomingMessageLoggerServiceBehaviorExtension, WcfExtensibilityTestServiceConsole"/> 14 </behaviorExtensions> 15 </extensions>參考資料
- Extending WCF with Custom Behaviors
- WCF Extensibility – Message Inspectors
- WCF擴(kuò)展
- Introduction to Extensibility
- Carlos Figueira MSDN blog
?
總結(jié)
以上是生活随笔為你收集整理的真实世界:使用WCF扩展记录服务调用时间的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 与众不同 windows phone (
- 下一篇: Android系统移植与调试之-----