最近开发一套由客户方定制的服务,据说之前版本是通过C写的WebService。那个神奇的Service我是没见过。只是有一点,之前的验证过程居然是这样进行的:客户端发送账号、密码,Service进行验证。验证成功后,Service会将产生一个加密字符,以类似Session方式存储并发送一个加密字符串给客户端。之后客户端每次调用服务都发送这个加密字符串以供服务端进行验证合法性。虽然个人觉得极为变态,但是交涉未果。另外还有一点需求就是:服务端会有一个授权文件,这个授权文件里面存放的是对客户端接口调用的授权信息。
基于以上原因,考虑到WCF优秀的扩展性决定对他进行一定的扩展,以完成以上需求。
由于客户端每次发送加密字符串用作身份凭证、以及授权的需求,因此选择IParameterInspector进行扩展。
大家都知道通过配置的方式可以使程序更加灵活。那么我们应该考虑一下如下几个问题:
1、如何通过配置的方式实现扩展?
2、配置以后,怎样使我们的扩展对WCF框架生效呢。?
对于第一个问题,在WCF中,可以通过继承BehaviorExtensionElement来实现;第二个问题,将参数检测应用到终结点行为上就能实现我们预期的目标,在这个过程中还需要实现IEndpointBehavior接口。
开始介绍之前先看看IParameterInspector接口定义:
public interface IParameterInspector { /// <summary> /// 用于操作调用完成后 /// </summary> /// <param name="operationName">接口操作名称</param> /// <param name="outputs">调用参数</param> /// <param name="returnValue">返回值</param> /// <param name="correlationState">与BeforeCall的关联状态</param> void AfterCall(string operationName, object[] outputs, object returnValue, object correlationState) { } /// <summary> /// 用于操作调用前 /// </summary> /// <param name="operationName">接口操作名称</param> /// <param name="inputs">调用参数</param> /// <returns>AfterCall中的correlationState。如果AfterCall需要用到correlationState,就返回;否则返回null</returns> public object BeforeCall(string operationName, object[] inputs) { } }
实现扩展的步骤:
1、实现IParameterInspector接口:
public void AfterCall(string operationName, object[] outputs, object returnValue, object correlationState) { } public object BeforeCall(string operationName, object[] inputs) { //登陆接口不做检查 const string actionName = "Login"; if (actionName == operationName) { return null; } Dictionary<string, Operation> dictionary = GetAuthorizationInfo(); if (0 == dictionary.Count) { ServiceFaultException exception = GetFormContainer(ConfigSetting.Instance.GetAuthorizationFileNotExistContainerName()); throw new FaultException<ServiceFaultException>(exception, exception.Reason, FaultCode.CreateSenderFaultCode( exception.ErrorCode.ToString( CultureInfo.InvariantCulture), string.Empty)); } if (!dictionary.ContainsKey(operationName) || Operation.Deny == dictionary[operationName]) { ServiceFaultException exception = GetFormContainer(ConfigSetting.Instance.GetNotAccessContainerName()); throw new FaultException<ServiceFaultException>(exception, exception.Reason, FaultCode.CreateSenderFaultCode( exception.ErrorCode.ToString( CultureInfo.InvariantCulture), string.Empty)); } bool flag = inputs.Any(input => (null != input) && CacheManager.ContainsKey(input.ToString())); if (!flag) { var exception = GetFormContainer(ConfigSetting.Instance.GetClientTicketOutContainerName()); throw new FaultException<ServiceFaultException>(exception, exception.Details, FaultCode.CreateSenderFaultCode( new FaultCode( exception.ErrorCode.ToString( CultureInfo.CurrentCulture)))); } return null; }
2、实现IEndpointBehavior接口,以将参数检测应用到DispatchRuntime的Operations的参数检查器中:
internal class ValidEndpointBehavior : 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) { SynchronizedKeyedCollection<string, DispatchOperation> keyedCollection = endpointDispatcher.DispatchRuntime.Operations; foreach (var dispatchOperation in keyedCollection) { dispatchOperation.ParameterInspectors.Add(new ParameterInspector()); } } public void Validate(ServiceEndpoint endpoint) { } #endregion }
注:以上扩展我只应用在了服务端。如果想应用在客户端,就应对ApplyClientBehavior中进行实现
3、继承抽象类BehaviorExtesionElement,以在配置文件中配置参数检测器。
internal class ExstensionBehaviorElement : BehaviorExtensionElement { public override Type BehaviorType { get { return typeof (ValidEndpointBehavior); } } protected override object CreateBehavior() { return new ValidEndpointBehavior(); } }
4、应用扩展:
<system.serviceModel> <extensions> <behaviorExtensions> <add name="endpointExstention" type="WcfExtensions.ExstensionBehaviorElement, WcfExtensions, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/> </behaviorExtensions> </extensions> <bindings> <basicHttpBinding> <binding maxReceivedMessageSize="2147483647" name="vrvbasicBinding"> <readerQuotas maxStringContentLength="2147483647"/> </binding> </basicHttpBinding> </bindings> <serviceHostingEnvironment> <serviceActivations> <add service="VrvService.StateGrid.TerminalService" relativeAddress="TerminalService.svc"/> <add service="VrvService.StateGrid.MapRegionService" relativeAddress="MapRegionService.svc"/> <add service="VrvService.LoginService" relativeAddress="LoginService.svc"/> </serviceActivations> </serviceHostingEnvironment> <behaviors> <serviceBehaviors> <behavior> <serviceMetadata httpGetEnabled="true"/> <serviceDebug includeExceptionDetailInFaults="True"/> <dataContractSerializer maxItemsInObjectGraph="3000"/> </behavior> </serviceBehaviors> <endpointBehaviors> <behavior> <endpointExstention/> </behavior> </endpointBehaviors> </behaviors> </system.serviceModel>
最后对IParameterInspector接口中AfterCall 中参数correlationState进行一下验证。我们应该如何应用它?
我将IParameterInspector接口中BeforeCall改为如下:
public object BeforeCall(string operationName, object[] inputs) { //登陆接口不做检查 const string actionName = "Login"; if (actionName == operationName) { return "ParameterInspector operationName is Login"; } }
然后客户端进行调用,跟踪AfterCall,如下图:
由此可知如果想通过BeforeCall返回一些信息在AfterCall中进行处理,我们可以在BeforeCall实现中返回。