WCF進階:擴展EndpointBehavior實現全局參數驗證
上一篇文章WCF進階:擴展bindingElementExtensions支持對稱加密傳輸闡述了如何擴展BindElementExtension來支持在配置文件中配置服務或者客戶端代理,本文講述另外一種應用,通過實現IEndpointBehavior來全局驗證操作參數,并且進一步產生比較復雜的配置支持的實現。
WCF中在ClientOperation和DispatchOperation上都有一個名為ParameterInspectors的集合類屬性,這個集合里面存儲的是實現IParameterInspector的參數攔截器,它非常的簡單,只有兩個方法定義:
using System; namespace System.ServiceModel.Dispatcher { // 摘要: // 定義自定義參數檢查器實現的協定,有了該協定,就可在客戶端或服務進行調用之前或緊接著其調用,檢查或修改信息。 public interface IParameterInspector { // 摘要: // 在客戶端調用返回之后、服務響應發送之前調用。 // // 參數: // operationName: // 所調用的操作的名稱。 // // outputs: // 任何輸出對象。 // // returnValue: // 操作的返回值。 // // correlationState: // 從 System.ServiceModel.Dispatcher.IParameterInspector.BeforeCall(System.String,System.Object[]) // 方法返回的任何關聯狀態,或 null。 void AfterCall(string operationName, object[] outputs, object returnValue, object correlationState); // // 摘要: // 在發送客戶端調用之前、服務響應返回之后調用。 // // 參數: // operationName: // 操作的名稱。 // // inputs: // 客戶端傳遞到方法的對象。 // // 返回結果: // System.ServiceModel.Dispatcher.IParameterInspector.AfterCall(System.String,System.Object[],System.Object,System.Object) // 中,作為 correlationState 參數返回的關聯狀態。如果您不打算使用關聯狀態,則返回 null。 object BeforeCall(string operationName, object[] inputs); } }
通過該接口的定義,我們能清楚能夠在客戶端發送請求之前,在服務端返回響應之后檢查請求的參數,而在客戶端接受響應之后和服務端發送響應之前能檢查返回值。
我們實現名為MyParameterValidater的IParameterInspector,通過遍歷規則列表中的驗證器驗證參數,本文實現三種驗證器,NotBlank,Email,Phone分別驗證參數不能為空,為Email格式,為電話號碼或者移動電話格式。具體代碼:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.ServiceModel.Dispatcher; using System.Text.RegularExpressions; namespace RobinLib { public class MyParameterValidater:IParameterInspector { ICollection<ParameterValidator> Validators { get; set; } public MyParameterValidater() { } public MyParameterValidater(ICollection<ParameterValidator> validators) { Validators = validators; } #region IParameterInspector 成員 public void AfterCall(string operationName, object[] outputs, object returnValue, object correlationState) { //接到請求或者接到響應后 } private static bool isEmail(string inputEmail) { string strRegex = @"^([a-zA-Z0-9_\-\.]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([a-zA-Z0-9\-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$"; Regex re = new Regex(strRegex); if (re.IsMatch(inputEmail)) return (true); else return (false); } /// <summary> /// 檢測電話號碼 /// </summary> /// <param name="strPhone"></param> /// <returns></returns> private static bool checkPhone(string strPhone) { if (strPhone == "" || strPhone == null) { return false; } else { string phoneRegWithArea = "^[0][1-9]{2,3}[-]{0,1}[0-9]{5,8}$"; string phoneRegNoArea = "^[2-9]{1}[0-9]{5,8}$"; if (strPhone.Length > 9) { if (Regex.Match(strPhone, phoneRegWithArea, RegexOptions.Compiled).Success) { return true; } else { return false; } } else { if (Regex.Match(strPhone, phoneRegNoArea, RegexOptions.Compiled).Success) { return true; } else { return false; } } } } /// <summary> /// 檢測手機號碼 /// </summary> /// <param name="s"></param> /// <returns></returns> private static bool checkMobile(string s) { if (s == "" || s == null) { return false; } else { string regu = "^[1][3,5,8][0-9]{9}$"; if (Regex.Match(s, regu, RegexOptions.Compiled).Success) { return true; } else { return false; } } } /// <summary> /// 檢測電話、手機號碼(組合) /// </summary> /// <param name="PhoneNum"></param> /// <returns></returns> private static bool CheckBrokerMobile(string PhoneNum) { if (PhoneNum == "" || PhoneNum == null) { return false; } else { if (PhoneNum.Substring(0, 1).ToString() == "1") { if (!checkMobile(PhoneNum)) { return false; } else { return true; } } else { if (!checkPhone(PhoneNum)) { return false; } else { return true; } } } } public object BeforeCall(string operationName, object[] inputs) { foreach (ParameterValidator pv in Validators) { if (pv.ValidateMethodName == operationName) { if (inputs.Length >= pv.ValidateParameterIndex) { if (pv.ValidateType == "NotBlank") { if (string.IsNullOrEmpty(inputs[pv.ValidateParameterIndex].ToString())) { throw new Exception("操作:"+operationName+"第"+pv.ValidateParameterIndex+"個參數不能為空!"); } } else if (pv.ValidateType == "Email") { if (!isEmail(inputs[pv.ValidateParameterIndex].ToString())) { throw new Exception("操作:" + operationName + "第" + pv.ValidateParameterIndex + "參數必須滿足Email格式!"); } } else if (pv.ValidateType == "Phone") { if (!CheckBrokerMobile(inputs[pv.ValidateParameterIndex].ToString())) { throw new Exception("操作:" + operationName + "第" + pv.ValidateParameterIndex + "參數必須滿足電話號碼或者手機格式!"); } } } } } return null; } #endregion } }
驗證器的的類定義為:
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace RobinLib { public class ParameterValidator { public int ValidateParameterIndex { get; set; } public string ValidateMethodName { get; set; } public string ValidateType { get; set; } } }
將此ParameterInspector通過IEndpointBehavior,IOperationBehavior,IServiceBehavior等能將該ParameterInspector添加到ClientOperation和DispatchOperation中。我們實現的是IEndpointBehavior,代碼為:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.ServiceModel.Description; using System.ServiceModel.Dispatcher; namespace RobinLib { public class ParameterValidatorBehavior : IEndpointBehavior { ICollection<ParameterValidator> Validators { get; set; } public ParameterValidatorBehavior() { } public ParameterValidatorBehavior(ICollection<ParameterValidator> validators) { Validators = validators; } #region IEndpointBehavior 成員 public void AddBindingParameters(ServiceEndpoint endpoint, System.ServiceModel.Channels.BindingParameterCollection bindingParameters) { } public void ApplyClientBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.ClientRuntime clientRuntime) { foreach (ClientOperation op in clientRuntime.Operations) { op.ParameterInspectors.Add(new MyParameterValidater(Validators)); } } public void ApplyDispatchBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.EndpointDispatcher endpointDispatcher) { foreach (DispatchOperation dop in endpointDispatcher.DispatchRuntime.Operations) { dop.ParameterInspectors.Add(new MyParameterValidater(Validators)); } } public void Validate(ServiceEndpoint endpoint) { } #endregion } }
到此,我們已經能夠直接通過代碼的形式將自定義ParameterValidatorBehavior的應用到客戶端或者服務端Endpoint上。
但我們還會實現通過配置的形式應用該ParameterValidatorBehavior,首先我們需要解決如何在配置中傳遞ICollection<ParameterValidator> Validators 參數。WCF中配置集合有一個基類,名為:ConfigurationElementCollection,我們應該首先實現一個自定義的ConfigurationElementCollection。集合中的每一個項也會對應配置中的一個ConfigurationElement,這樣我們設計兩個類:ParameterValidatorCollection和ParameterValidatorElement和ParameterValidatorConfigElement用這三個類實現自定義ParameterValidatorBehavior的配置,形式為:
<parameterValidator> <parameterValidators> <add name="p1" validateMethodName="AddUser" validateParameterIndex="0" validateType="Email"/> <add name="p2" validateMethodName="AddUser" validateParameterIndex="1" validateType="NotBlank"/> <add name="p3" validateMethodName="AddUser" validateParameterIndex="2" validateType="Phone"/> </parameterValidators> </parameterValidator>
下面給出三個類的代碼實現:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.ServiceModel.Configuration; using System.Configuration; namespace RobinLib { public sealed class ParameterValidatorElement : BehaviorExtensionElement { public ParameterValidatorElement() { } [ConfigurationProperty("parameterValidators", IsDefaultCollection = false)] public ParameterValidatorCollection ParameterValidators { get { ParameterValidatorCollection parameterValidators = (ParameterValidatorCollection)base["parameterValidators"]; return parameterValidators; } } public override Type BehaviorType { get { return typeof(ParameterValidatorBehavior); } } protected override object CreateBehavior() { List<ParameterValidator> validators = new List<ParameterValidator>(); foreach (ParameterValidatorConfigElement ve in ParameterValidators) { ParameterValidator pv = new ParameterValidator(); pv.ValidateMethodName = ve.ValidateMethodName; pv.ValidateParameterIndex = Convert.ToInt32(ve.ValidateParameterIndex); pv.ValidateType = ve.ValidateType; validators.Add(pv); } ParameterValidatorBehavior behavior = new ParameterValidatorBehavior(validators); return behavior; } } }
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Configuration; namespace RobinLib { public class ParameterValidatorCollection : ConfigurationElementCollection { protected override ConfigurationElement CreateNewElement() { return new ParameterValidatorConfigElement(); } protected override ConfigurationElement CreateNewElement(string elementName) { return new ParameterValidatorConfigElement(elementName); } protected override object GetElementKey(ConfigurationElement element) { return ((ParameterValidatorConfigElement)element).Name; } public override ConfigurationElementCollectionType CollectionType { get { return ConfigurationElementCollectionType.AddRemoveClearMap; } } public ParameterValidatorConfigElement this[int index] { get { return (ParameterValidatorConfigElement)BaseGet(index); } set { if (BaseGet(index) != null) { BaseRemoveAt(index); } BaseAdd(index, value); } } new public ParameterValidatorConfigElement this[string Name] { get { return (ParameterValidatorConfigElement)BaseGet(Name); } } public int IndexOf(ParameterValidatorConfigElement validater) { return BaseIndexOf(validater); } public void Add(ParameterValidatorConfigElement validater) { BaseAdd(validater); } public void Remove(ParameterValidatorConfigElement validater) { if (BaseIndexOf(validater) >= 0) { BaseRemove(validater.Name); } } public void RemoveAt(int index) { BaseRemoveAt(index); } public void Remove(string name) { BaseRemove(name); } public void Clear() { BaseClear(); } } }
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Configuration; namespace RobinLib { public class ParameterValidatorConfigElement : ConfigurationElement { public ParameterValidatorConfigElement(String newName, String validateParameterIndex, string validateMethodName, string validateType) { Name = newName; ValidateParameterIndex = validateParameterIndex; ValidateMethodName = validateMethodName; ValidateType = validateType; } public ParameterValidatorConfigElement(string newName) { Name = newName; } public ParameterValidatorConfigElement() { } [ConfigurationProperty("name")] public string Name { get { return base["name"] as string; } set { base["name"] = value; } } [ConfigurationProperty("validateParameterIndex")] public string ValidateParameterIndex { get { return base["validateParameterIndex"] as string; } set { base["validateParameterIndex"] = value; } } [ConfigurationProperty("validateMethodName")] public string ValidateMethodName { get { return base["validateMethodName"] as string; } set { base["validateMethodName"] = value; } } [ConfigurationProperty("validateType")] public string ValidateType { get { return base["validateType"] as string; } set { base["validateType"] = value; } } } }
實現了這些,我們就配置中應用ParameterValidatorBehavior了。好我們先來看下服務契約的設計
using System; using System.Collections.Generic; using System.Linq; using System.Runtime.Serialization; using System.ServiceModel; using System.Text; namespace Robin_Wcf_ParameterValidater_SvcLib { // 注意: 如果更改此處的類名“IService1”,也必須更新 App.config 中對“IService1”的引用。 public class Service1 : IService1 { public int AddUser(string email, string pwd, string phone) { Console.WriteLine("new user,email:" + email + ",phone" + phone+",pwd="+pwd); return 1; } } }
將該服務托管到一個ConsoleApp中并在一個Console中調用。并且在客戶端發送請求和服務端發送響應的時候,要求操作AddUser的參數email必須為Email格式,pwd不能為空,phone是電話或者手機格式。這樣設計好的服務的配置文件為:
<?xml version="1.0" encoding="utf-8" ?> <configuration> <system.serviceModel> <services> <clear/> <service name="Robin_Wcf_ParameterValidater_SvcLib.Service1"> <host> <baseAddresses> <add baseAddress="http://127.0.0.1:8081"/> </baseAddresses> </host> <endpoint name="ep" address ="" binding ="basicHttpBinding" contract="Robin_Wcf_ParameterValidater_SvcLib.IService1" behaviorConfiguration="myEpBehavior"/> </service> </services> <behaviors> <endpointBehaviors> <behavior name="myEpBehavior"> <parameterValidator> <parameterValidators> <add name="p1" validateMethodName="AddUser" validateParameterIndex="0" validateType="Email"/> <add name="p2" validateMethodName="AddUser" validateParameterIndex="1" validateType="NotBlank"/> <add name="p3" validateMethodName="AddUser" validateParameterIndex="2" validateType="Phone"/> </parameterValidators> </parameterValidator> </behavior> </endpointBehaviors> </behaviors> <extensions> <behaviorExtensions> <add name="parameterValidator" type="RobinLib.ParameterValidatorElement, RobinLib, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/> </behaviorExtensions> </extensions> </system.serviceModel> </configuration>
托管代碼為:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.ServiceModel; using Robin_Wcf_ParameterValidater_SvcLib; namespace Robin_Wcf_ParameterValidater_Host { class Program { static void Main(string[] args) { ServiceHost host = new ServiceHost(typeof(Service1)); if (host.Description.Behaviors.Find<System.ServiceModel.Description.ServiceMetadataBehavior>() == null) { System.ServiceModel.Description.ServiceMetadataBehavior svcMetaBehavior = new System.ServiceModel.Description.ServiceMetadataBehavior(); svcMetaBehavior.HttpGetEnabled = true; svcMetaBehavior.HttpGetUrl = new Uri("http://127.0.0.1:8001/Mex"); host.Description.Behaviors.Add(svcMetaBehavior); } host.Opened += new EventHandler(delegate(object obj, EventArgs e) { Console.WriteLine("服務已經啟動!"); }); host.Open(); Console.Read(); } } }
客戶端配置為:
<?xml version="1.0" encoding="utf-8" ?> <configuration> <system.serviceModel> <bindings> <basicHttpBinding> <binding name="ep" closeTimeout="00:01:00" openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00" allowCookies="false" bypassProxyOnLocal="false" hostNameComparisonMode="StrongWildcard" maxBufferSize="65536" maxBufferPoolSize="524288" maxReceivedMessageSize="65536" messageEncoding="Text" textEncoding="utf-8" transferMode="Buffered" useDefaultWebProxy="true"> <readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384" maxBytesPerRead="4096" maxNameTableCharCount="16384" /> <security mode="None"> <transport clientCredentialType="None" proxyCredentialType="None" realm=""> <extendedProtectionPolicy policyEnforcement="Never" /> </transport> <message clientCredentialType="UserName" algorithmSuite="Default" /> </security> </binding> </basicHttpBinding> </bindings> <client> <endpoint address="http://127.0.0.1:8081/" binding="basicHttpBinding" bindingConfiguration="ep" contract="ServiceReference1.IService1" name="ep" behaviorConfiguration="myEpBehavior" /> </client> <behaviors> <endpointBehaviors> <behavior name="myEpBehavior"> <parameterValidator> <parameterValidators> <add name="p1" validateMethodName="AddUser" validateParameterIndex="0" validateType="Email"/> <add name="p2" validateMethodName="AddUser" validateParameterIndex="1" validateType="NotBlank"/> <add name="p3" validateMethodName="AddUser" validateParameterIndex="2" validateType="Phone"/> </parameterValidators> </parameterValidator> </behavior> </endpointBehaviors> </behaviors> <extensions> <behaviorExtensions> <add name="parameterValidator" type="RobinLib.ParameterValidatorElement, RobinLib, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/> </behaviorExtensions> </extensions> </system.serviceModel> </configuration>
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Robin_Wcf_ParameterValidater_ClientApp { class Program { static void Main(string[] args) { try { System.Threading.Thread.Sleep(6000); ServiceReference1.Service1Client svc = new Robin_Wcf_ParameterValidater_ClientApp.ServiceReference1.Service1Client(); svc.AddUser("robinzhang", "", ""); } catch(Exception ex) { Console.WriteLine(ex.Message); } try { System.Threading.Thread.Sleep(6000); ServiceReference1.Service1Client svc = new Robin_Wcf_ParameterValidater_ClientApp.ServiceReference1.Service1Client(); svc.AddUser("jillzhang@126.com", "123456", ""); } catch (Exception ex) { Console.WriteLine(ex.Message); } try { System.Threading.Thread.Sleep(6000); ServiceReference1.Service1Client svc = new Robin_Wcf_ParameterValidater_ClientApp.ServiceReference1.Service1Client(); svc.AddUser("jillzhang@126.com", "", "010-88888888"); } catch (Exception ex) { Console.WriteLine(ex.Message); } Console.Read(); } } }
運行結果如下:
項目文件:點擊這里下載
需要額外注意的是:添加EndpointBehavior有個特別的要求,<add name="parameterValidator" type="RobinLib.ParameterValidatorElement, RobinLib, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>中的type值必須和ParameterValidatorElement的強類型的表達字符串完全相符,增減空格或者回車都不行,這也可以認為.net framework的一個bug,詳情見:connect.microsoft.com/wcf/feedback/details/216431/wcf-fails-to-find-custom-behaviorextensionelement-if-type-attribute-doesnt-match-exactly,最終都無人能解。也害得我在這上面郁悶了很久,總以為自己的類編寫錯誤了呢,后來才發現原來和配置BindingElementExtension有此區別,希望大家也多多注意,
出處:http://jillzhang.cnblogs.com/
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利。

浙公網安備 33010602011771號