上一篇文章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有此区别,希望大家也多多注意,