利用反射解决QueryString和Session中的参数绑定问题
这里是示例代码
利用反射解决QueryString和Session中的参数绑定问题
一. 前言
本文主要译自网上的一篇文章(http://www.codeproject.com/aspnet/WebParameter.asp),自己又作了一些改进,因此形成了这篇心得。
二. 简介:
本文介绍了在ASP.NET页面对象中利用元数据自动从Page.Request对象的QueryString中填充页面的变量和属性的策略.
三. 问题的提出
当你对ASP.NET页面进行参数化时,比如:从Request.QueryString(GET)或Request.Form(POST)中获取参数,你需要花大量的时间写类似以下的代码:
protected string FirstName 

{ 
    get 
 
{ 
     return Request.QueryString["FirstName"];  
    } 
} 
 
但是你必须处理潜在的FirstName为null值是的情况,因此你可能会通过如下的代码提供一个缺省值 

protected static string IsNull(string test, string defaultValue)
{ 
    return test==null ? defaultValue : test; 
} 
 
protected string FirstName_NullSafe
{ 
   get
{ return IsNull(Request.QueryString["FirstName"],""); } 
} 
 对于非字符串类型,你也可以通过稍微修改以上代码来决定当参数为null时是提供一个缺省值还是抛出一个异常,只是要用到Convert.ToXXX()函数

protected int CustomerID
{ 
    get
 {
                object o=Request.QueryString["CustomerID"]; 
                if (o==null)//如果为空,则抛出异常 
                { throw new ApplicationException("Customer ID is required to be passed");
}
                else {

                    try
 {
//如果不空,转换成Int32型
                            return Convert.ToInt32(o,10); 
                    } catch(Exception err)
 {
                            throw new ApplicationException("Invalid CustomerID", err); 
                      } 
                } 
       } 
} 
 
因此,在Page_Load函数里你可能会写如下的代码进行参数初始化

private void Page_Load(object sender, System.EventArgs e) 
 {
        string o; 
 
        // 提供一个初始值 
        FirstName2 =IsNull(Request.QueryString["FirstName"], ""); 
 
        //确保该值不为空,否则抛出异常 
        o =Request.QueryString["CustomerID"]; 
        if (o==null) 
            throw new  ApplicationException("Customer ID is required to be passed"); 
        else 
          try
{//如果转换类型不正常,抛出异常 
                CustomerID2 = Convert.ToInt32(o,10); 
          } catch(Exception err)
{ 
                throw new ApplicationException("Invalid CustomerID", err); 
           } 
 
 } 
 小结:
当你在处理从Request.QueryString或Request.Form读取参数时,有以下的固定模式
- 从 
Request.QueryString(或Request.Form)中检索值, - 当检索的值为空时抛出异常或提供一个缺省值
 - 将检索到的值转换成合适的数据成员(field)或属性(property)
 - 如果检索值不能正确转换类型,抛出一个异常或提供一个缺省值
 
四. 解决方案:声明性参数绑定
l 表现形式:
一个解决的方案是使用元数据(metadata),并让一些函数执行实际的工作
比如说以下形式:
 [WebParameter()] 
protected string FirstName; 
//如果没有提供参数,则表示查找和FirstName同名的关键字, 
//即QueryString[“FirstName”], 
 
[WebParameter("Last_Name")] 
protected string LastName; 
//表示从QueryString中查找"Last_Name"的变量值 
//即QueryString[“Last_Name”] 
 
[WebParameter(IsRequired=true)] 
protected int CustomerID; 
//表示如果QueryString[“CustomerID”]返回空值,则抛出异常 
 可选的构造函数提供了从Request.QueryString中查找参数的关键字,IsRequired属性决定了当该参数缺失时是否抛出异常。   
l 思路
本解决方案的思路是,利用反射来检索到附加在变量上的元数据,然后根据该元数据的属性决定如何对变量进行存取,其存取模式采用如第2节(《问题的提出》)最后一部分小结里的模式,只是改成了由程序自动判断进行。
1、 检索元数据属性
如前所述,当先定义一个从System.Attribute派生的元数据类WebParameterAttribut, 
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple=false, Inherited=true)] 
public class WebParameterAttribute : Attribute 

{ 
        public int ParamName; 
 
        public string DefaultValue 
        
{ 
            get
{    return _default    ;   } 
            set
{    _default=value    ;    } 
     } 
} 
 
说明: 
[WebParameter("Last_Name")] 
protected string LastName; 
 
    当以上面形式使用自定义的属性类时,实际上等与是生成了一个和LastName的类型对象LastName.GetType()相关联的WebParameterAttribute类的对象,通过LastName.GetType()对象可以检索到WebParameter对象的一些方法成员、数据成员以及属性等; 
我们可以定义WebParameterAttribute类的静态方法,WebParameterAttribute.SetValues(object target, System.Web.HttpRequest request)完成自动初始化某个页面的所有参数,该方法有两个参数:target表示将被自动进行参数化的对象,例如可以是某个页面对象(Page);request是在某个页面对象的初始化时传来的Request对象;后面我们会详细解释SetValues方法的设计
示例如下面粗体部分所示: 
public class WebParameterDemo : System.Web.UI.Page 
    
{ 
        [WebParameter("txtFirstName")] 
        public string FirstName        ="field default"; 
 
        [WebParameter("txtLastName", DefaultValue="attribute default")] 
        public string LastName
{ 
            get
{    return (string)ViewState["LastName"];    } 
            set
{    ViewState["LastName"]=value;            } 
        } 
 
        [WebParameter ("txtCustomerID", DefaultValue="0")] 
        public int CustomerID; 
 
        int TestNum;//注意该数据成员没有关联WebParameter属性 
 
        protected System.Web.UI.HtmlControls.HtmlInputText txtFirstName; 
        protected System.Web.UI.HtmlControls.HtmlInputText txtLastName; 
        protected System.Web.UI.HtmlControls.HtmlInputText txtCustomerID; 
 
        private void Page_Load(object sender, System.EventArgs e) 
        
{ 
            WebParameterAttribute.SetValues(this, Request); 
            //注意这里是用法 
            txtFirstName.Value    =FirstName; 
            txtLastName.Value    =LastName; 
            txtCustomerID.Value    =CustomerID.ToString(); 
        } 
     
 
    在上面的示例中,静态方法WebParameterAttribut.SetValues先获取this指针代表的Page对象的类型对象this.GetType(),然后用该类型对象获取Page对象的所有变量和属性的信息,即可以获得并操纵Page对象的FirstName,LastName和CustomerID,也包括TestNum。然后SetValues方法检查每个检索到的变量和属性,看有没有关联WebParameter对象,相对来说,数据成员TestNum就没有关联WebParameter对象。 
当SetValues检测到变量(field)或属性(property)关联有WebParameter对象时,该方法会检索和变量或属性向关联的WebParameter对象的具体属性,比如说: 
[WebParameter("Last_Name")] 
protected string LastName; 
 
生成了一个WebParameter对象,并传递给构造函数一个参数”Last_Name”,SetValues方法会检测出和string LastName相关联的WebParameter中包含的“Last_Name”并将其作为要在QueryString中检测的变量的名字。 
l 代码解析
下面是SetValues的具体实现:
²        变量及属性定义 
    [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple=false, Inherited=true)] 
    public class WebParameterAttribute : Attribute 
    
{ 
        定义私有变量#region 定义私有变量 
                string _default;    //缺省值 
                string _name;    //要从QueryString中检测的变量的名称 
                bool _isRequired;    //是否为必须值,如果缺失则抛出异常 
                //如果传递的参数类型转换失败,是否设缺省值 
                bool _isDefaultForInvalid;     
 
            #endregion 
 
        Constructors#region Constructors 
                //构造函数,传递paraName,以便用QueryString[paraName]检索 
                public WebParameterAttribute(string paramName) 
        
{ 
                    _name        =paramName; 
               } 
        #endregion 
         
 
        public string ParameterName 
        
{ 
            get
{    return _name;    } 
            set
{    _name=value;    } 
            } 
     
        public string DefaultValue 
        
{ 
            get
{    return _default    ;    } 
            set
{    _default=value    ;    } 
            } 
     
        public bool IsRequired 
        
{ 
            get
{    return _isRequired;    } 
            set
{    _isRequired=value;    } 
           } 
     
        /**//// </summary> 
        public bool IsDefaultUsedForInvalid 
        
{ 
            get
{    return _isDefaultForInvalid;    } 
            set
{    _isDefaultForInvalid=value;    } 
        } 
 
² SetValues方法
从request中检索特定值并设定target中指定变量和属性的值 
public static void SetValues(object target, System.Web.HttpRequest request)  
 

{//传入的target对象可以是Page对象,request可以是Page.Request对象  
 
        //获取target的类型信息  
 
         System.Type type    =target.GetType();  
 
   
 
          
 
         //获取target公开的、实例化后的成员信息  
 
         MemberInfo[] members= type.GetMembers(BindingFlags.Instance |  
 
         BindingFlags.Public|  
 
        BindingFlags.NonPublic);  
 
        //Instance:只对非静态的成员查找;  
 
        //Public:只对具有公开访问属性的成员进行查找  
 
        //在和Instance或static联用时,  
 
        //必须指定NonPublic或Public,否则返回空值  
 
   
 
         foreach(MemberInfo member in members)  
 
      
{  
 
             if((member is FieldInfo) || (member is PropertyInfo))  
 
          
{//对变量和属性,设置其初始值  
 
                     SetValue(member, target, request);  
 
             }  
 
         }  
 
}  
 
 
² SetValue方法
根据member对象的类型是变量(Field)或属性(Property),从request中检索特定值并设置target.member的值 
public static void SetValue(MemberInfo member, object target, System.Web.HttpRequest request)  
 

{  
 
         WebParameterAttribute[] attribs;  
 
         WebParameterAttribute attrib;  
 
         TypeConverter converter;  
 
         string paramValue;  
 
         string paramName;  
 
         object typedValue;  
 
         bool usingDefault;  
 
   
 
         try  
 
         
{  
 
                 attribs=(WebParameterAttribute[])member.GetCustomAttributes(typeof(WebParameterAttribute), true);  
 
                 //返回和变量或属性关联的WebAttribute对象或其子类的对象  
 
   
 
                 if(attribs!=null && attribs.Length>0)  
 
              
{  
 
                         //对于索引属性,不支持,所谓索引属性,  
 
                         //是指C#中可以用类似数组下标形式访问的属性  
 
                         if (member.MemberType==MemberTypes.Property)  
 
                      
{  
 
                             ParameterInfo[] ps=  ((PropertyInfo)member).GetIndexParameters();  
 
                             if (ps!=null && ps.Length>0)  
 
                                   throw new NotSupportedException("Cannot apply WebParameterAttribute to indexed property");  
 
 
                 }  
 
   
 
                 //因为WebAttribute在每个成员上只能应用一次,  
 
                //所以取第一个就行了  
 
                attrib=attribs[0];  
 
                 //获取[WebParameter(“Last_Name”)]中的”Last_Name”字符串  
 
                 //如果是类似[WebParameter()] string Name;形式,则  
 
                 //得到的名字是”Name”,和变量或属性的名字同名  
 
                paramName=(attrib.ParameterName!=null)? attrib.ParameterName : member.Name;  
 
                  
 
                 //下面将获取paramValue值,但是是string类型  
 
                 //WebParameter自定义方法GetValue,后面有说明  
 
                //从request对象获取QueryString[paramName]值  
 
                //或是Form[paramName]值  
 
                 paramValue=attrib.GetValue(paramName, request);  
 
   
 
                 //如果在QueryString中未检索到paramName所对应的值  
 
                 usingDefault    =false;  
 
                if (paramValue==null)  
 
             
{  
 
                     if (attrib.DefaultValue!=null)  
 
                  
{//如果设定了缺省值,则用缺省值定赋值  
 
                         paramValue  =attrib.DefaultValue;  
 
                         usingDefault    =true;  
 
                     }  
 
                     else if (!attrib.IsRequired)  
 
                   
{//如果没有定义缺省值,且不要求一定有值,返回  
 
                         return; // 仅仅跳过该值  
 
                     }  
 
                     else  
 
                   
{//如果要求必须有值,且未检索到值,则抛出异常  
 
                         throw new ApplicationException(  
 
                                    String.Format(  
 
                                            "Missing required parameter '{0}'",  
 
                                            paramName)  
 
                          );  
 
                 }  
 
   
 
                 //下面将string类型的paramValue  
 
                //根据变量或属性的类型作转换,存放在typedValue中  
 
                 //对变量或属性,类型转换稍有不同  
 
   
 
                  
 
                //根据成员是变量(FieldType),属性(PropertyType)  
 
                //两种情况获取member的Type类型对象,  
 
                //如果不是变量或属性,则抛出异常  
 
                //注意:FiledInfo和PropertyInfo,MethodInfo  
 
                //都是MemberInfo的子类  
 
                //获取成员对应的类型转换器  
 
                converter=TypeDescriptor.GetConverter(  
 
                            GetMemberUnderlyingType(member));  
 
                if(converter==null || !converter.CanConvertFrom(paramValue.GetType()))  
 
             
  {//如果转换器为空或不能从字符串类型转换,抛出异常  
 
                            throw new ApplicationException(  
 
                                    String.Format(  
 
                                            "Could not convert from {0}",  
 
                                            paramValue.GetType()  
 
                                    )  
 
                        );  
 
                  }  
 
   
 
                 try  
 
              
{//将paramValue从字符串类型转换成  
 
                     //对应成员变量或属性的类型  
 
                     typedValue=converter.ConvertFrom(paramValue);  
 
                     SetMemberValue(member, target, typedValue);  
 
                     //注:自定义SetMemberValue,将target.member的值  
 
                     //设为同类型的typedValue;  
 
                 }  
 
                 catch  
 
               
{  
 
                     // 这里捕获类型转换和在设置变量或属性中的异常错误  
 
                     //如果定义了缺省值,且确定当发生无效值时使用缺省值  
 
                     //设定变量或属性为缺省值  
 
                     if (!usingDefault && attrib.IsDefaultUsedForInvalid && attrib.DefaultValue!=null)  
 
                   
{  
 
                         typedValue  =converter.ConvertFrom( attrib.DefaultValue);  
 
                         SetMemberValue(member, target, typedValue);  
 
                     }  
 
                     else  
 
                         throw;  
 
                 }  
 
             }  
 
         }  
 
         catch(Exception err)  
 
         
{  
 
             throw new ApplicationException( "Property/field {0} could not be set from request - " + err.Message,err);  
 
          }  
 
     }  
 
   
 
 
² GetValue方法
从request中检索名paramName对应的值,返回字符串 
protected virtual string GetValue(string paramName, System.Web.HttpRequest request)  
 

{//该函数可以重载,以支持Session,QueryString,Form,ViewState类型  
 
         if (request.HttpMethod.ToLower()=="post")  
 
             return request.Form[paramName];  
 
         else  
 
             return request.QueryString[paramName];  
 
      
 
}  
 
 
² GetMemberUnderlyingType方法
获取变量或属性对应的类型变量 
private static Type GetMemberUnderlyingType(MemberInfo member)  
 

{  
 
//获取member的Type类型对象,如果不是变量或属性,则抛出异常  
 
//注意:FiledInfo和PropertyInfo,MethodInfo都是MemberInfo的子类  
 
     switch(member.MemberType)  
 
     
{  
 
         case MemberTypes.Field:  
 
             return ((FieldInfo)member).FieldType;  
 
         case MemberTypes.Property:  
 
             return ((PropertyInfo)member).PropertyType;  
 
         default:  
 
             throw new ArgumentException(  
 
"Expected a FieldInfo or PropertyInfo",  
 
"member");  
 
     }  
 
 
² SetMemberValue方法
根据变量或属性两种情况对成员进行赋值 
private static void SetMemberValue(MemberInfo member, object target, object value)  
 

{//对变量(Field)和属性(Property)的SetValue方法不太一样,  
 
//所以要分成两种情况考虑  
 
   
 
     switch(member.MemberType)  
 
     
{  
 
         case MemberTypes.Field:  
 
             ((FieldInfo)member).SetValue(target, value);  
 
             break;  
 
   
 
         case MemberTypes.Property:  
 
             // 我们已经知道该属性不是索引属性,  
 
            //所以该属性不会是数组,设置序号为0的属性就可以了  
 
             ((PropertyInfo)member).SetValue(  
 
                        target,  
 
                       value,  
 
                        new Object[0]//对序号为0的属性设置  
 
            );  
 
             break;  
 
     }  
 
}  
 
 五. 改进
为了实际解决QueryString和Session的参数化问题,对WebParameterAttribute类做了些修改,同时以WebParameterAttribute类为基类派生了QueryParameterAttribute类和SessionParameterAttribute类,分别负责QueryString和Session的参数化。
具体做法如下:
在WebParameterAttribute类中添加了两类静态函数,一类是用于从QueryString,Session,ViewState中读取数据到Page类的成员变量中去。相应的实现函数分别是SetValuesFromBuffer,SetValueFromBuffer,SetMemberValue;另一类用于将Page类的成员变量值写入到Session,ViewState的变量中去。相应的实现函数分别示SetValuesToBuffer,SetValueToBuffer,SetBufferValue。这两类函数的具体实现请看程序源代码,原理和前面讲述的一样。
六. 有待改进的地方
以参数BindingFlags.NonPublic调用Type.GetMembers方法时,系统规定反射代码只能存取被反射对象的protected及public访问类型的成员,要访问private成员,需要用到ReflectionPermission类赋于代码一定权限,而MSDN和其他的资料里关于如何使用ReflectionPermission类访问被反射对象的private成员语焉不详。所以目前本解决方案中还访问不到Page对象的private成员,而只能访问protected和public成员。
七. 技术文档
l AttributeUsageAttribute类
形如
[MyTest(value,attr1=value1,attr2=value2)]
的代码实际上是用类MyTestAttribute定义了一个对象MyTestAttribute(value),而value则是传递的构造函数的参数,可以不止一个,但是要按照定义构造函数时参数的顺序传递。而形如attr1=value1,attr2=value2,则是在MyTestAttribute类中定义了attr1,attr2属性(property),可以在生成MyTestAttribute的对象时给这些属性赋值,这是可选的。
因此,前面的代码表明了
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple=false, Inherited=true)]
实际上是AttributeUsageAttribute类生成的一个对象,传递给构造函数的参数为AttributeTargets.Field | AttributeTargets.Property,同时给两个命名属性赋了值。
关于AttributeUsageAttribute,MSDN里的说明是:定义您自己的特性类时,可通过在特性类上放置 AttributeUsageAttribute 来控制特性类的使用方式。指示的特性类必须直接或间接地从 Attribute 派生。
换句话说,我们定义一个自己的特性类WebParameterAttribute类时,要从Attribute派生,同时用AttributeUsageAttribute的对象来控制自己的WebParameterAttribute类将来可以怎样被使用。
向构造函数传递的参数为AttributeTargets类型的枚举变量,有以下几种组合:
Class:可以将自定义的特性类用于一个类
例:
[AttributeUsage(AttributeTargets.Class)] 
public class ClassTargetAttribute : Attribute 

{ 
} 
 
[ClassTarget] 
public class TestClass 

{ 
} 
 
//错误的用法 
[ClassTarget] 
public int CustomerID; 
//因为ClassTargetAttribute由 
//[AttributeUsage(AttributeTargets.Class)]表明了只能用于类 
 
Field:可以将自定义的特性类用于一个成员变量
例:
[AttributeUsage(AttributeTargets.Field)] 
public class FieldTargetAttribute : Attribute 

{ 
} 
 
[FieldTarget] 
public int Count; 
 
Property:可以将自定义的特性类用于一个属性
例:
[AttributeUsage(AttributeTargets.Property)]public class PropertyTargetAttribute : Attribute{} [PropertyTarget]public string Name{get{return m_name;}
set{m_name=value;}
}
AttributeUsageAttribute的两个命名参数AllowMultiple=false, Inherited=true,其中AllowMultiple是指是否允许在同一个对象上多次使用自定义的属性类,而Inherited指明该属性是否可以有派生类:
例如:
[AttributeUsage(AttributeTargets.Property,AllowMultiple=false)]
public class PropertyTargetAttribute(string Name) : Attribute{}//错误的用法,因为AllowMultiple指明了
//不允许在同一对象上多次使用该属性
[PropertyTarget(“John”)]
[PropertyTarget(“Tom”) ]
class TestClass{}
To:SetValues
当在派生类中重写时,使用指定绑定约束,搜索为当前 Type 定义的成员。
[Visual Basic]Overloads Public MustOverride Function GetFields( _ByVal bindingAttr As BindingFlags _
) As FieldInfo() Implements IReflect.GetFields
[C#]public abstract MemberInfo[] GetMembers(
BindingFlags bindingAttr
); [Visual Basic]Overloads Public MustOverride Function GetMembers( _ByVal bindingAttr As BindingFlags _
) As MemberInfo() Implements IReflect.GetMembers
[C#] [C++]public: virtual MemberInfo* GetMembers(
BindingFlags bindingAttr
) [] = 0;[JScript]public abstract function GetMembers(bindingAttr : BindingFlags
) : MemberInfo[];
 [C++]public: virtual FieldInfo* GetFields(
BindingFlags bindingAttr
) [] = 0;[JScript]public abstract function GetFields(bindingAttr : BindingFlags
) : FieldInfo[];
参数
可以使用下列 BindingFlags 筛选标志定义包含在搜索中的字段:
为了获取返回值,必须指定 BindingFlags.Instance 或 BindingFlags.Static。
指定 BindingFlags.Public 可在搜索中包含公共字段。
指定 BindingFlags.NonPublic 可在搜索中包含非公共字段(即私有字段和受保护的字段)。
指定 BindingFlags.FlattenHierarchy 可包含层次结构上的静态成员。
下列 BindingFlags 修饰符标志可用于更改搜索的执行方式:
BindingFlags.DeclaredOnly,表示仅搜索在 Type 上声明的字段,
而不搜索简单继承的字段。 注意 :
必须与 Public 或 NonPublic 一起指定 Instance 或 Static,否则将不返回成员。
有关更多信息,请参见 System.Reflection.BindingFlags。
2、 MemberInfo. GetCustomAttributes
To:SetValue
在派生类中被重写时,返回由 Type 标识的自定义特性的数组。
[Visual Basic]Overloads Public MustOverride Function GetCustomAttributes( _ByVal attributeType As Type, _
ByVal inherit As Boolean _
) As Object() Implements ICustomAttributeProvider.GetCustomAttributes
[C#]public abstract object[] GetCustomAttributes(
Type attributeType,
bool inherit
);[C++]public: virtual Object* GetCustomAttributes(
Type* attributeType,
bool inherit
)  __gc[] = 0;[JScript]public abstract function GetCustomAttributes(attributeType : Type,
inherit : Boolean
) : Object[];
参数
attributeType
要搜索的属性类型。只返回可分配给此类型的属性。
inherit
指定是否搜索该成员的继承链以查找这些属性。

                
            
        
浙公网安备 33010602011771号