真实世界:使用WCF扩展记录服务调用时间
WCF 可扩展性
WCF 提供了许多扩展点供开发人员自定义运行时行为。 WCF 在 Channel Layer 之上还提供了一个高级运行时,主要是针对应用程序开发人员。在 WCF 文档中,它常被称为服务模型层(Service Model Layer)。该高级运行时主要由一个称作 Dispatcher(在 ServiceHost 的 Context 中)的组件和一个称作 Proxy(在客户端的 Context 中)的组件组成。
(图片引自 MSDN Magazine : Extending WCF with Custom Behaviors)
每个扩展点都使用接口定义来扩展。
Stage | Interceptor Interface | Description |
---|---|---|
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 是一种特殊类型的类,它在 ServiceHost/ChannelFactory 初始化过程中扩展运行时行为。WCF 有四种类型的行为:服务行为、终结点行为、契约行为和操作行为。
Scope | Interface | Potential Impact | |||
---|---|---|---|---|---|
Service | Endpoint | Contract | Operation | ||
Service | IServiceBehavior | ✗ | ✗ | ✗ | ✗ |
Endpoint | IEndpointBehavior | ✗ | ✗ | ✗ | |
Contract | IContractBehavior | ✗ | ✗ | ||
Operation | IOperationBehavior | ✗ |
每种行为类型也是通过不同的接口定义来扩展,它们都共用一组相同的方法。一个例外是,IServiceBehavior 没有 ApplyClientBehavior 方法,因为服务行为不能用于客户端。
Method | Description |
---|---|
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扩展点
(图片引自 lovecindywang = lovecherry 博客)
案例:使用WCF扩展记录服务调用时间
服务定义:
[ServiceContract]
public interface ICalculatorService
{
[OperationContract]
int Add(int a, int b);
}
服务实现:
public class CalculatorService : ICalculatorService
{
public int Add(int a, int b)
{
return a + b;
}
}
配置文件:
<system.serviceModel>
<services>
<service name="WcfExtensibilityTestServiceConsole.CalculatorService">
<endpoint address="" binding="netTcpBinding" bindingConfiguration=""
contract="WcfExtensibilityTestServiceConsole.ICalculatorService"
name="myCalculatorServiceEndpoint"/>
<endpoint address="mex" binding="mexTcpBinding" contract="IMetadataExchange"/>
<host>
<baseAddresses>
<add baseAddress="net.tcp://localhost:12345/CalculatorService"/>
</baseAddresses>
</host>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior>
<serviceMetadata/>
<serviceDebug includeExceptionDetailInFaults="true"/>
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
问题描述
现在需要记录每次服务调用执行所需的时间,比如可以将测量的方法执行时间传递给 PerformanceCounter 用于性能计数,或者直接写入到日志中等。
方式一:直接在方法内测量
public class CalculatorService : ICalculatorService
{
public int Add(int a, int b)
{
Stopwatch watch = Stopwatch.StartNew(); int result = a + b;
Thread.Sleep(TimeSpan.FromMilliseconds()); // waste time here Debug.WriteLine(string.Format("Method [{0}] execution cost [{1}] milliseconds.",
"Add", watch.ElapsedMilliseconds)); return result;
}
}
这种方法浅显易懂,但如果服务所提供的功能过多,会导致大量冗余代码。
方式二:形成测量类简化代码
public class Measure : IDisposable
{
private Stopwatch _watch = null; protected Measure(string methodName)
{
MethodName = methodName;
_watch = new Stopwatch();
} public string MethodName { get; private set; } public void Start()
{
_watch.Start();
} public void Stop()
{
_watch.Stop(); Debug.WriteLine(string.Format("Measure method [{0}] execution cost [{1}] milliseconds.",
MethodName, _watch.ElapsedMilliseconds));
} public static Measure StartNew(string methodName)
{
Measure m = new Measure(methodName);
m.Start();
return m;
} public void Dispose()
{
Stop();
_watch = null;
}
}
public class CalculatorService : ICalculatorService
{
public int Add(int a, int b)
{
using (var measure = Measure.StartNew("Add"))
{
Thread.Sleep(TimeSpan.FromMilliseconds()); // waste time here
return a + b;
}
}
}
此种方式简化了代码,但仍然需要在各自方法内实现,并需提供方法名作为参数。
使用 Message Inspection 来解决问题
我们定义类 IncomingMessageLoggerInspector 来实现 IDispatchMessageInspector 接口。
#region IDispatchMessageInspector Members public object AfterReceiveRequest(
ref Message request,
IClientChannel channel,
InstanceContext instanceContext)
{
var context = OperationContext.Current;
if (context == null) return null; var operationName = ParseOperationName(context.IncomingMessageHeaders.Action); return MarkStartOfOperation(
context.EndpointDispatcher.ContractName, operationName,
context.SessionId);
} public void BeforeSendReply(ref Message reply, object correlationState)
{
var context = OperationContext.Current;
if (context == null) return; var operationName = ParseOperationName(context.IncomingMessageHeaders.Action); MarkEndOfOperation(
context.EndpointDispatcher.ContractName, operationName,
context.SessionId, correlationState);
} #endregion
通过服务的当前上下文实例,我们可以获取到服务被调用的契约名称 ContractName,并且可以在 IncomingMessageHeaders 总解析出被调用的 OperationName。
我们在方法 MarkStartOfOperation 中启动 Stopwatch 开始测量执行时间,在方法执行完毕后服务模型会调用 BeforeSendReply 并将 Stopwatch 实例引用传递至 correlationState,此时我们可以在方法 MarkEndOfOperation 中解决时间测量,并打印日志。
#region Private Methods private static string ParseOperationName(string action)
{
if (string.IsNullOrEmpty(action)) return action; string actionName = action; int index = action.LastIndexOf('/');
if (index >= )
{
actionName = action.Substring(index + );
} return actionName;
} private static object MarkStartOfOperation(
string inspectorType, string operationName, string sessionId)
{
var message = string.Format(CultureInfo.InvariantCulture,
"Operation [{0}] was called at [{1}] on [{2}] in thread [{3}].",
operationName, inspectorType,
DateTime.Now.ToString(@"yyyy-MM-dd HH:mm:ss.ffffff", CultureInfo.InvariantCulture),
Thread.CurrentThread.ManagedThreadId); Debug.WriteLine(message); return Stopwatch.StartNew();
} private static void MarkEndOfOperation(
string inspectorType, string operationName,
string sessionId, object correlationState)
{
var watch = (Stopwatch)correlationState;
watch.Stop(); var message = string.Format(CultureInfo.InvariantCulture,
"Operation [{0}] returned after [{1}] milliseconds at [{2}] on [{3}] in thread [{4}].",
operationName, watch.ElapsedMilliseconds, inspectorType,
DateTime.Now.ToString(@"yyyy-MM-dd HH:mm:ss.ffffff", CultureInfo.InvariantCulture),
Thread.CurrentThread.ManagedThreadId); Debug.WriteLine(message);
} #endregion
此时,我们需要定义 EndpointBehavior 来讲 Inspector 设置到 DispatchRuntime 中。
public class IncomingMessageLoggerEndpointBehavior : IEndpointBehavior
{
#region IEndpointBehavior Members public void AddBindingParameters(
ServiceEndpoint endpoint,
BindingParameterCollection bindingParameters)
{
} public void ApplyClientBehavior(
ServiceEndpoint endpoint,
ClientRuntime clientRuntime)
{
} public void ApplyDispatchBehavior(
ServiceEndpoint endpoint,
EndpointDispatcher endpointDispatcher)
{
if (endpointDispatcher != null)
{
endpointDispatcher.DispatchRuntime.MessageInspectors.Add(
new IncomingMessageLoggerInspector());
}
} public void Validate(ServiceEndpoint endpoint)
{
} #endregion
}
然后,我们在 ServiceHost 实例化后,未Open前,将 IncomingMessageLoggerEndpointBehavior 添加至 Endpoint 的 Behaviors 中。
class Program
{
static void Main(string[] args)
{
ServiceHost host = new ServiceHost(typeof(CalculatorService)); foreach (var endpoint in host.Description.Endpoints)
{
endpoint.Behaviors.Add(new IncomingMessageLoggerEndpointBehavior());
} host.Opened += new EventHandler(delegate(object obj, EventArgs e)
{
Debug.WriteLine(typeof(CalculatorService).Name + " 服务已经启动!");
}); host.Open(); Console.ReadKey();
}
}
使用 WcfTestClient.exe 调用服务,执行 2 + 3 查看结果,
在 Debug 输出中可以看到,
使用配置文件定制
使用配置文件定制扩展的优点就是可以按需添加和删除扩展,而无需改动代码。比如当发现系统有性能问题时,添加该扩展来查看具体哪个方法执行速度慢。
需要定义类来实现 BehaviorExtensionElement 抽象类。
public class IncomingMessageLoggerEndpointBehaviorExtension : BehaviorExtensionElement
{
public override Type BehaviorType
{
get { return typeof(IncomingMessageLoggerEndpointBehavior); }
} protected override object CreateBehavior()
{
return new IncomingMessageLoggerEndpointBehavior();
}
}
在配置文件中添加扩展项,
<extensions>
<behaviorExtensions>
<add name="incomingMessageLogger"
type="WcfExtensibilityTestServiceConsole.Extensions.IncomingMessageLoggerEndpointBehaviorExtension, WcfExtensibilityTestServiceConsole"/>
</behaviorExtensions>
</extensions>
在终结点行为中添加该扩展定义,
<behaviors>
<serviceBehaviors>
<behavior>
<serviceMetadata/>
<serviceDebug includeExceptionDetailInFaults="true"/>
</behavior>
</serviceBehaviors>
<endpointBehaviors>
<behavior>
<incomingMessageLogger/>
</behavior>
</endpointBehaviors>
</behaviors>
使用 ServiceBehavior 扩展
同理,如果服务实现了多个 Endpoint,则想在所有 Endpoint 上添加该扩展,除了可以逐个添加或者使用 behaviorConfiguration 来配置。
另一个方法是可以借助 IServiceBehavior 的扩展实现。
public class IncomingMessageLoggerServiceBehavior : IServiceBehavior
{
#region IServiceBehavior Members public void AddBindingParameters(
ServiceDescription serviceDescription,
ServiceHostBase serviceHostBase,
Collection<ServiceEndpoint> endpoints,
BindingParameterCollection bindingParameters)
{
} public void ApplyDispatchBehavior(
ServiceDescription serviceDescription,
ServiceHostBase serviceHostBase)
{
if (serviceHostBase != null)
{
foreach (ChannelDispatcher channelDispatcher in serviceHostBase.ChannelDispatchers)
{
foreach (EndpointDispatcher endpointDispatcher in channelDispatcher.Endpoints)
{
endpointDispatcher.DispatchRuntime.MessageInspectors.Add(
new IncomingMessageLoggerInspector());
}
}
}
} public void Validate(
ServiceDescription serviceDescription,
ServiceHostBase serviceHostBase)
{
} #endregion
}
原理相同,仅是遍历通道分配器中所有的终结点,逐一添加 Inspector。
同理,如果需要在配置文件中使用,也需要实现一个 BehaviorExtensionElement 类。
public class IncomingMessageLoggerServiceBehaviorExtension : BehaviorExtensionElement
{
public override Type BehaviorType
{
get { return typeof(IncomingMessageLoggerServiceBehavior); }
} protected override object CreateBehavior()
{
return new IncomingMessageLoggerServiceBehavior();
}
}
此时的配置文件描述如下:
<behaviors>
<serviceBehaviors>
<behavior>
<serviceMetadata/>
<serviceDebug includeExceptionDetailInFaults="true"/>
<incomingMessageLogger/>
</behavior>
</serviceBehaviors>
</behaviors>
<extensions>
<behaviorExtensions>
<add name="incomingMessageLogger"
type="WcfExtensibilityTestServiceConsole.Extensions.IncomingMessageLoggerServiceBehaviorExtension, WcfExtensibilityTestServiceConsole"/>
</behaviorExtensions>
</extensions>
参考资料
- Extending WCF with Custom Behaviors
- WCF Extensibility – Message Inspectors
- WCF扩展
- Introduction to Extensibility
- Carlos Figueira MSDN blog
真实世界:使用WCF扩展记录服务调用时间的更多相关文章
- 使用WCF扩展记录服务调用时间
随笔- 64 文章- 0 评论- 549 真实世界:使用WCF扩展记录服务调用时间 WCF 可扩展性 WCF 提供了许多扩展点供开发人员自定义运行时行为. WCF 在 Channel Lay ...
- WCF扩展记录服务调用时间
WCF 提供了许多扩展点供开发人员自定义运行时行为. WCF 在 Channel Layer 之上还提供了一个高级运行时,主要是针对应用程序开发人员.在 WCF 文档中,它常被称为服务模型层(Serv ...
- 使用WCF扩展在方法调用前初始化环境
使用WCF扩展在方法调用前初始化环境 OperationInvoker 介绍 OperationInvoker 是 WCF 运行时模型中在调用最终用户代码前的最后一个扩展点,OperationInvo ...
- 真实世界:使用WCF扩展在方法调用前初始化环境
OperationInvoker 介绍 OperationInvoker 是 WCF 运行时模型中在调用最终用户代码前的最后一个扩展点,OperationInvoker 负责最终调用 Service ...
- WCF扩展
WCF 可扩展性 WCF 提供了许多扩展点供开发人员自定义运行时行为. WCF 在 Channel Layer 之上还提供了一个高级运行时,主要是针对应用程序开发人员.在 WCF 文档中,它常被称为服 ...
- 在MVC或WEBAPI中记录每个Action的执行时间和记录下层方法调用时间
刚才在博客园看了篇文章,http://www.cnblogs.com/cmt/p/csharp_regex_timeout.html 突然联想到以前遇到的问题,w3wp进程吃光CPU都挂起IIS进程 ...
- WCF 服务调用 QueryRun
通过AX2012的WCF服务调用AX2012的方法时,如果方法里调用了QueryRun对象时,会报错,报错信息如下:System.ServiceModel.FaultException: 无法将类型为 ...
- WCF服务调用方式
WCF服务调用通过两种常用的方式:一种是借助代码生成工具SvcUtil.exe或者添加服务引用的方式,一种是通过ChannelFactory直接创建服务代理对象进行服务调用.
- 跟我一起学WCF(11)——WCF中队列服务详解
一.引言 在前面的WCF服务中,它都要求服务与客户端两端都必须启动并且运行,从而实现彼此间的交互.然而,还有相当多的情况希望一个面向服务的应用中拥有离线交互的能力.WCF通过服务队列的方法来支持客户端 ...
随机推荐
- centos环境下使用percona-xtrabackup对mysql5.6数据库innodb和myisam进行快速备份及恢复
centos环境下使用percona-xtrabackup对mysql5.6数据库innodb和myisam进行快速备份及恢复 有时候我们会碰到这样的业务场景: 1.将大的数据库恢复到本地进行业务测试 ...
- 开发常用小demo 整理
pc懒加载 https://github.com/ningmengxs/Lazy_loading 元素滑动 js 效果 https://github.com/ningmengxs/elem ...
- C# 接口应用及意义
写在前面:新手入行,读者勉强看看吧,写的不对的欢迎讨论,板砖轻拍! 一.定义 接口描述的是可属于任何类或结构的一组相关功能,所以实现接口的类或结构必须实现接口定义中指定的接口成员. 通常用Interf ...
- 模拟ATM机将输入的数据插入数据库
ATM抽象类 public abstract class ATM { private double balance; private String idcard; private String pas ...
- zk源码环境搭建
zk不是使用maven管理的. 将zk的src下的代码导入eclipse,lib下的jar包导入工程. QuorumPeerMain类的main方法是入口,启动了zk的server,参数是conf文件 ...
- TortoiseSVN文件夹及文件状态图标不显示解决方法
win8 64位系统,原本svn是好用的,安装了klive金山快盘后,svn图标都不显示了.最后通过修改注册表解决: win+R调出运行框,输入regedit,打开注册表编辑器. HKEY_LOCAL ...
- 8.11 CSS知识点4
边框样式 1.边框宽度 border-width:medium | thin | thick | length border-top-width 设置上边框宽度 border-bottom-widt ...
- C语言提供了几个标准库函数 itoa() atoi()
C语言提供了几个标准库函数C语言提供了几个标准库函数,可以将任意类型(整型.长整型.浮点型等)的数字转换为字符串.以下是用itoa()函数将整数转换为字符串的一个例子: # include <s ...
- Adobe Dreamweaver(DW)
下载破解版地址:http://www.frontopen.com/1179.html 详情地址:http://baike.baidu.com/link?url=8Jv88BJ-wXeyABAbYEMl ...
- SharedPreferences的基本数据写入和读取
1.布局 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android ...