• 利用反射解决QueryString和Session中的参数绑定问题


    注:去年写的一篇文章了,现在把它贴出来,以做参考
    这里是示例代码
    利用反射解决
    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 
    指定是否搜索该成员的继承链以查找这些属性。 

     

  • 相关阅读:
    c语言:猴子吃桃问题
    c语言:输入任意数求该数的阶乘
    (整理三)高并发架构思路,附十万定时任务执行解决方案
    (整理4)RPC服务和HTTP服务简单说明
    .NET Core和Swagger 生成 Api 文档转
    (整理二)读取大日志文件
    (整理一)理解分布式事务,高并发下分布式事务的解决方案-附索引的利弊
    2016年结
    2013年结
    2017年结
  • 原文地址:https://www.cnblogs.com/dragon/p/125160.html
Copyright © 2020-2023  润新知