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);
    }
}

通过该接口的定义,我们能清楚能够在客户端发送请求之前,在服务端返回响应之后检查请求的参数,而在客户端接受响应之后和服务端发送响应之前能检查返回值。

image

我们实现名为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();
        }
    }
}

运行结果如下:

image

项目文件:点击这里下载

需要额外注意的是:添加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有此区别,希望大家也多多注意,

posted @ 2010-04-18 19:31 Robin Zhang 阅读(...) 评论(...) 编辑 收藏