摩诘

我思故我在 常辨而常新

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::
  23 随笔 :: 2 文章 :: 479 评论 :: 14 引用
注:去年写的一篇文章了,现在把它贴出来,以做参考
这里是示例代码
利用反射解决
QueryStringSession中的参数绑定问题

一.     前言

本文主要译自网上的一篇文章(http://www.codeproject.com/aspnet/WebParameter.asp),自己又作了一些改进,因此形成了这篇心得。

 

二.     简介:

本文介绍了在ASP.NET页面对象中利用元数据自动从Page.Request对象的QueryString中填充页面的变量和属性的策略.

 

三.     问题的提出

当你对ASP.NET页面进行参数化时,比如:从Request.QueryStringGET)或Request.FormPOST)中获取参数,你需要花大量的时间写类似以下的代码:

protected string FirstName
{
    
get
 
{
     
return Request.QueryString["FirstName"]; 
    }

}

 


但是你必须处理潜在的
FirstNamenull值是的情况,因此你可能会通过如下的代码提供一个缺省值

protected static string IsNull(string test, string defaultValue){
    
return test==null ? defaultValue : test;
}


protected string FirstName_NullSafe{
   
getreturn 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.QueryStringRequest.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    ;    }
     }

}

 

注:AttributeUsageAttribute使用说明

 

说明:


[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对象的FirstNameLastNameCustomerID,也包括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
    
{
        
定义私有变量

        
Constructors
        

        
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

     }
 

}
 


五.     改进

为了实际解决QueryStringSession的参数化问题,对WebParameterAttribute类做了些修改,同时以WebParameterAttribute类为基类派生了QueryParameterAttribute类和SessionParameterAttribute,分别负责QueryStringSession的参数化。

具体做法如下:

WebParameterAttribute类中添加了两类静态函数,一类是用于从QueryStringSessionViewState中读取数据到Page类的成员变量中去。相应的实现函数分别是SetValuesFromBufferSetValueFromBufferSetMemberValue;另一类用于将Page类的成员变量值写入到SessionViewState的变量中去。相应的实现函数分别示SetValuesToBufferSetValueToBufferSetBufferValue。这两类函数的具体实现请看程序源代码,原理和前面讲述的一样。

六.     有待改进的地方

以参数BindingFlags.NonPublic调用Type.GetMembers方法时,系统规定反射代码只能存取被反射对象的protectedpublic访问类型的成员,要访问private成员,需要用到ReflectionPermission类赋于代码一定权限,而MSDN和其他的资料里关于如何使用ReflectionPermission类访问被反射对象的private成员语焉不详。所以目前本解决方案中还访问不到Page对象的private成员,而只能访问protectedpublic成员。

 

 

七.     技术文档

l         AttributeUsageAttribute

形如

MyTestvalue,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,同时给两个命名属性赋了值。

关于AttributeUsageAttributeMSDN里的说明是:定义您自己的特性类时,可通过在特性类上放置 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.PropertyAllowMultiple=false)]
public class PropertyTargetAttributestring Name : Attribute
{
}
//错误的用法,因为AllowMultiple指明了
//不允许在同一对象上多次使用该属性
[PropertyTarget(John)]
[PropertyTarget(Tom) ]
class TestClass
{
}

 

 

l         获取类的类型信息

ToSetValues

1、             Type. GetMembers

当在派生类中重写时,使用指定绑定约束,搜索为当前 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 
指定是否搜索该成员的继承链以查找这些属性。 

 

posted on 2005-03-24 21:54  sema  阅读(...)  评论(...编辑  收藏