最近開發一套由客戶方定制的服務,據說之前版本是通過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實現中返回。