• 4/页面生命周期


    页面的生命周期一般只指从请求页面到卸载页面的过程。这之间又具体分以下几个阶段。

    1、页请求:页请求发生在页面生命周期开始之前,用户请求页面时,asp.net将确定是否需要分析和编译页。

    2、开始:在开始阶段,将设置页属性,如request和response。在此阶段,页还将确定请求是回发请求还是新请求,并设置ispostback属性。

    3、页初始化:在初始化期间可以使用页中的控件。并设置控件的ID属性。

    4、加载:在加载期间,如果当前请求是回发请求,则将使用视图状态和控件状态恢复的信息加载控件属性。

    5、验证:在验证期间,将调用所有的验证程序控件的validate,此方法将设置各个验证程序控件和页的isvalidate属性。

    6、回发事件处理:如果请求是回发请求,则将调用所有的事件处理程序。

    7、呈现:在呈现之前,会对页和所有控件保存视图状态。在呈现阶段中,会对每个控件调用render方法,它会提供一个文本编写器,用户将控件的输出写入到response属性和outputstream中。

    8、卸载:完全呈现页,并将页面发送到客户端,准备对其该页后,将调用卸载。此时将卸载页属性并执行清理。

    u    浏览器的能力

    ASP.NET开发的软件属B/S软件,B就是Brower,即浏览器,也就是说,我们写的东西,最终是需要浏览器来展示的。我们可以这样认为,浏览器只认识3个东西:html、css和JavaScript,html主要用于描述控件的类别,如<inputid="Text1"type="text"/>表示一个文本框,css用于描述html控件的外观,当然,html也可以直接描述外观,但为了使html代码简单清晰,同时便于多个html控件共享外观,通常将外观描述部分分离出来,单独放到一个地方,这就是css。JavaScript是编程语言,用于对html控件进行操作。当然,借助插件,浏览器还可以认识视频、flash,甚至U盾等等,不过,这些都是插件的功劳,并不是浏览器与生俱来的,有关插件的相关知识超出了本文的范畴,建议参照其他相关资料。

    既然浏览器只认识html、css和JavaScript,那么,我们在ASPX页面中布局的服务器控件、在cs代码中修改的东西等等,发送到客户端时,最终都必须转换成标准的html、css和JavaScript,而我们在网页上看到的光怪陆离的效果,其实,都是标准的html控件“模拟”出来的,为此,我们做一个试验,新建一个aspx页面,拖一个文本框上去,在aspx页面中,对应的代码(其他无关代码省略了)是:

    <asp:TextBoxID="TextBox1"runat="server"></asp:TextBox>

    运行后,在浏览器上点右键,查看源文件,发现浏览器收到的实际代码是:

    <inputname="TextBox1"type="text"id="TextBox1" />

    我们可以看到,服务器控件到了客户端,会被转换成标准的HTML控件,如果我们新建一个HTML文件,直接把服务器控件的描述(<asp:TextBox/>放进去,浏览器是不认的。

    u    服务器事件

    当我们点一下页面上的按钮,页面好像自动调用了服务器上的Click方法,同样,其他很多操作,如下拉框(设置AutoPostBack为True)的当前选择项更改时,都自动调用了后台对应的方法,貌似和普通C/S软件差不多,其实,这些也是“模拟”的,可以说,为了让B/S软件开发起来和C/S差不多,“模拟”功不可没,服务器真正收到的,只有一个事件:页面请求。

    u     页面访问方式

    我们知道,页面访问有两种方式:Get和Post,那么,到底该如何区分呢?我们来看两种情况:

    1.直接输入网址,如http://www.lqjt.com,当然,还可以输入一些参数,如:http://www.lqjt.com?id=5,当我们直接在浏览器地址栏里面输入地址(包括带参数的地址),然后按回车就可以访问页面了,而此时,服务器收到的,仅仅是一个网址名称,这种轻量级的访问,就是我们所说的Get。

    2.当页面已经打开时,假如页面上有一个按钮,我们点击该按钮,页面会回传,也就是重新读取页面,这时,浏览器就不是简单把网址发给服务器了,而是将当前页面中的内容作为附加信息,连同网址一起发送给服务器,也就是说,服务器收到的信息,包括网址和附加信息,这就是Post方式。

    也就是说,Get方式收到的仅仅是网址,没有附加信息,而Post方式,除了网址,还要当前页面的相关内容信息,注意,网址后面的“?id=5”可以认为是网址的一部分,和Post访问中的附加信息是两回事,最直观的区别是,一个是直接在地址栏输入地址访问,另外一个是页面回传。

    u     服务器端和客户端的顺序

    总有人问:C#能否调用客户端的JavaScript啊,或者JavaScript能否调用C#的方法啊,其实服务器端和客户端是“接力棒”的关系,服务器端完成任务后,立即就释放了,也就是说,浏览器收到服务器返回的信息时,服务器端的页面已经不存在了,因此,这种互相访问是不存在的,或许你会说,为什么Ajax方法可以呢?需要说明的是,Ajax访问服务器时,访问的也是新建立的页面,而不是“刚才”的页面,浏览器接收到新页面后,刚才的页面内容全部扔了,也是典型的“喜新厌旧”,因此,你也不要指望新页面达到后,使用老页面的控件、变量等,如果需要保存变量,一般采用cookie。

    有了上面的准备,我们开始进入正题。我们说了,服务器收到的,其实只有一个事件:页面请求,各种事件都是模拟的,而服务器返回的,是标准的HTML。当我们向服务器发送页面请求时,服务器新建一个页面,然后进行处理,处理完成后,把最终结果返回给浏览器,同时释放刚刚生成的页面,整个页面从诞生到消亡的整个过程,我们称之为页面生命周期,或者叫Page类编程模型,页面的生命周期是非常短暂的,每次请求,就产生一个独立的、全新的页面,本次请求结束后,立即被释放。因为每次都是全新的,因此,不要期望页面保存“上一次请求”的信息,这就是所谓的“无状态”。为了“模拟”C/S,ASP.NET将页面生命周期分成40多个阶段,不过常用也就几个,比如我们最熟悉的“Page_Load”阶段,每个阶段都有特定的目的,为了便于描述,我们假如整个服务器端就是一个方法,当然,并不是服务器上真的有该方法,而是我们希望用大家熟悉的C#来描述整个过程。

    我们定义这样一个方法来描述整个页面生命周期:

    ///<summary>

       ///模拟页面生命周期

       ///</summary>

       ///<paramname="url">页面的网址</param>

       ///<paramname="Content">附加信息</param>

       ///<returns>浏览器能解析的标准HTML</returns>

       publicstring AspWebService(stringurl,byte[] Content)

       {

           string strHTML ="";

           return strHTML;

       }

    我们创建了上面一个公共方法来模拟整个页面生命周期,浏览器调用AspWebService方法,传入页面的地址(url)和当前页面的信息(Content),服务器创建新的页面,并完成各个阶段,最后,产生一个标准的HTML返回给浏览器,浏览器接收以后,把标准的HTML显示出来,下面,我们将几个关键的过程完成,看看最终页面是如何生成的。

    当我们在地址栏里面输入网址并回车,页面请求就开始了,浏览器调用服务器的AspWebService方法。因为我们直接输入的地址,没有附加消息,属Get方式访问,Content当然就是null为此,我们需要声明一个变量,来表示是否有附加消息

           bool IsPostBack;

           if (Content ==null)

               IsPostBack =false;

           else

               IsPostBack =true;

    IsPostBack大家已经非常熟悉了,如果是第一次打开页面,IsPostBack为False,否则,为True,显然,第一次输入网站访问时,IsPostBack为False,为减少篇幅,把上面的代码写到一句话里面,完成后的方法为:

       publicstring AspWebService(stringurl,byte[] Content)

       {

           bool IsPostBack=(Content ==null)? false: true;

           string strHTML ="";

           return strHTML;

       }

    接下来,读入aspx页面中的内容,并根据aspx页面中定义的控件,转换成对应的C#内存变量,这里,我们用一个字符串变量表示读入的aspx页面信息:

    string strAspxLayout ="......<asp:ButtonID='Button1' runat='server' Text='Button' />....";

    当然,一个aspx页面的内容非常多,我们只截取其中的一个按钮,前后的其他控件部分用省略号代替,根据aspx的布局,创建对应的变量:

    Button btn =newButton();

    btn.ID ="Button1";

    btn.Text ="Button";

    显然,上面的办法是将aspx页面中的控件“翻译”成C#能认识的控件,便于我们操作,现在的代码变成:

    publicstring AspWebService(string url,byte[]Content)

       {

          bool IsPostBack=(Content ==null)?false:true;

     

    string strAspxLayout ="…….<asp:ButtonID='Button1' runat='server' Text='Button' />……";

           if (strAspxLayout.Length > 0)

           {

               //根据<asp:Button>创建按钮,其他控件类推

               Button btn =newButton();

               btn.ID ="Button1";

               btn.Text ="Button";

           }

     

           string strHTML ="";

           return strHTML;

       }

    控件创建好以后,将调用我们常用的第一个方法:Page_Init();该方法的具体用处我们后面会讲到。

    接下来,根据是否是页面回传做必要的处理,因为我们本次是用Get方式访问的,因此,这一步我们暂时空着,等下次页面回传时再逐步完善,现在的代码如下:

    publicstring AspWebService(string url,byte[]Content)

       {

          bool IsPostBack=(Content ==null)?false:true;

           string strAspxLayout ="......<asp:ButtonID='Button1' runat='server' Text='Button' />....";

           if (strAspxLayout.Length > 0)

           {

               Button btn =newButton();

               btn.ID ="Button1";

               btn.Text ="Button";

           }

     

           //调用常用的第一个方法

           Page_Init(this,newEventArgs());

           //如果是页面回传,进行必要处理,暂时空着

           if (IsPostBack)

           {

           }

     

           string strHTML ="";

           return strHTML;

       }

    接下来,调用第二个方法,也就是我们最熟悉的方法:Page_Load,我们很多工作都是在Page_Load里面做的

    //调用最常用的方法

    Page_Load(this,newEventArgs());

    接下来,看看有没有其他事件需要响应,比如button1_Click什么的现在的代码如下:

    publicstringAspWebService(string url,byte[] Content)

       {

          bool IsPostBack=(Content ==null)?false:true;

           string strAspxLayout ="......<asp:ButtonID='Button1' runat='server' Text='Button' />....";

           if (strAspxLayout.Length > 0)

           {

               Button btn =newButton();

               btn.ID ="Button1";

               btn.Text ="Button";

           }

           Page_Init(this,newEventArgs());

           if (IsPostBack)

           {

           }

     

           //调用Page_Load方法

           Page_Load(this,newEventArgs());

           //如果有其他事件需要执行,在这里调用

           Button1_Click(this,newEventArgs());

     

           string strHTML ="";

           return strHTML;

       }

    服务器端该做的事情差不多了,接下来,要准备发送到客户端了,前面说过,浏览器只认识HTML、CSS、JavaScript,“Button btn=new Button()”这样的显然是不行的,因此,我们要把当前内存中的服务器控件转换成浏览器能认识的标准HTML控件,比如,把按钮转换成<inputtppe=’submit’/>之类。该过程需要我们调用Render方法,在一个真实的页面中,我们可以重载下面的方法修改默认的HTML:

    protectedoverridevoid Render(HtmlTextWriterwriter)

       {

           base.Render(writer);

       }

    如果没有重载,将使用默认的,在这里,我们就是要模拟Render方法,也就是说,我们要准备strHTML变量的内容,HTML其他部分我们忽略,这里示意一下按钮对应的HTML代码:

    //准备浏览器能认识的HTML文本

    string strHTML ="......<inputtype='submit' name='Button1' value='Button' id='Button1' />....";

    内存中的控件和标准的HTML控件并不是可以完全互相转换的,在很多方面差别还很大,尤其是C#控件有大量属性,而HTML控件相对简单,在经过一系列操作(尤其是Page_Load和Button1_Click等方法处理后,或者动态添加、删除了部分控件),内存中的TextBox等控件已经面目全非了,前面说过了,B/S是无状态的,本次结束后,内存中的全部东西都会被释放,为了便于下次能“还原”到当前的样子,我们需要把内存中的控件等信息保存起来,这就是ViewState。例如一个文本框,有宽度、高度、文本内容、颜色等信息,我们把这些东西序列化,或者说,转换成一个字符串,然后和上面的html放一起,作为返回值的一部分。为此,我们创建一个隐藏域,值就是序列化以后的内容:

    string strViewState ="<inputtype='hidden' name='__VIEWSTATE' id='__VIEWSTATE'value='/wEPDwUKLTExMjgzODMzMWRk+oeVKURRouiTt3dBl+gaw3M+9Ds=' />";

    和上面的HTML组合起来:

           strHTML += strViewState;

    显然,strViewState中,value的内容就是当前页面各个控件(严格来说,用户可以在里面保存任何东西,比如变量值等等)转换而来的,我们把ViewState放到HTML里面,作为HTML的一部分,我们编写的模拟Render方法如下:

    privatestring Render()

       {

           string strHTML ="......<inputtype='submit' name='Button1' value='Button' id='Button1' />....";

           string strViewState ="<inputtype='hidden' name='__VIEWSTATE' id='__VIEWSTATE'value='/wEPDwUKLTExMjgzODMzMWRk+oeVKURRouiTt3dBl+gaw3M+9Ds=' />";

           strHTML += strViewState;

           return strHTML;

    }

    也就是说,生成的标准的HTML控件代码,是给浏览器看的,且用户可以更改(比如输入文字),而将内存中的控件序列化后,放隐藏域里面的目的是便于下一次还原,达到持久化的目的,且用户无法修改,但我们返回给客户端的只能是一个字符串,而当前内存控件很快要被释放,为此,我们将内存控件序列化后得到的字符串附加到前面的HTML中去,将HTML作为一个临时存放的场所。

    然后在主程序里面调用上面的Render方法,得到的代码如下:

    publicstringAspWebService(string url,byte[] Content)

       {

           bool IsPostBack=(Content ==null)?false:true;

           string strAspxLayout ="......<asp:ButtonID='Button1' runat='server' Text='Button' />....";

           if (strAspxLayout.Length > 0)

           {

               Button btn =newButton();

               btn.ID ="Button1";

               btn.Text ="Button";

           }

           Page_Init(this,newEventArgs());

           if (IsPostBack)

           {

           }

           Page_Load(this,newEventArgs());

           Button1_Click(this,newEventArgs());

     

           //准备浏览器能认识的HTML文本

           string strHTML = Render();

     

           return strHTML;

       }

    最后一步,很简单,就是把strHTML返回给客户端,然后再释放整个页面占用的内存,至此,第一轮的整个页面周期结束,浏览器得到strHTML后,呈现给用户,用户看到的就是漂亮的页面了。

    前面讲的是Get方式访问页面的整个生命周期,其中,还留了一点没有做,下面我们来看第二种方式:Post方式是如何进行的,两种方式基本上一样,只不过Post要多一点点东西,就是我们上面未完成的。

    现在,页面上已经有很多东西,用户输入完信息,点“提交”按钮,整个页面会被回传,新的页面生命周期又开始了,会重复上面的过程,不过,这次是按钮提交的,不是手工输入的网址,除了网址外,还包括当前页面的信息,比如文本框中用户输入的内容,下拉框当前选择等等,我们把这些保存在Content参数中,也就是说,这一次访问,Content是有内容的,内容就是当前客户端页面的全部信息,而不是null,我们来看看是如何进行的。

    1.     <![endif]>设置IsPostback变量,这一步完全一样

    2.     <![endif]>读入aspx页面内容,并转换成内存变量,这一步也是一样的

    3.     <![endif]>调用Page_Init方法,也一样

    4.     <![endif]>处理回传内容,也就是上面的if(IsPostBack)等待我们完成,现在我们就来完成

    根据Content参数的内容,重新设置一下前面3步创建的控件,比如,页面设计时,默认的文本框内容是空的,现在,用户输入了内容,那么,就必须执行this.TextBox1.Text="用户输入的新内容",通过aspx页面的布局,结合客户端返回的值(保存在Content中),可以创建一个客户端当前页面的信息。

    还记得前面的ViewState吗?那是保存上一次页面在服务器时的状态的,该变量也是在Content的一部分,我们把ViewState取出来,再根据ViewState新建一个页面,这时,我们是不是得到了前一次页面的样子了?

    换句话说,我们根据Content中的内容,创建了一个客户端当前页面,根据ViewState还原了上一次服务器端的页面,这样,我们就有两个页面了,一个是当前页面,也就是经过客户端用户操作以后的页面,一个是上一次在服务器端的页面,也就是通过ViewState还原的页面,接下来,我们比较一下两个页面,假如文本框内容不一样,那么,我们就登记一个事件,TextBoxChanged事件,只不过,该事件不会马上执行,我们先记着,同样,也要记着可能产生的其他事件,比如下拉框当前选项改变等等,而Content中除了有控件当前状态等信息,还有一个信息就是,该页面回传是谁产生的,比如是按钮产生的,那么,也要登记一个Click事件,因此,这部分代码我们可以用下面的方法模拟:

    if (IsPostBack)

           {

               Page page_Cur =this;

               //根据Content还原成客户端的样子

               this.TextBox1.Text ="new value";

               //还原上一次页面的样子

               Page page_Last =newPage();

               string strViewState ="内容从Content中的ViewState隐藏域提取";

               if (strViewState.Length > 0)

               {

                   //根据strViewState创建控件并添加到page_Last中

               }

               //比较两个页面和根据事件源登记事件

               foreach (Controlctlin page_Cur.Form.Controls)

               {

                   foreach (Controlctl2inpage_Last.Controls)

                   {

                       if (ctl.ID == ctl2.ID)

                       {

                           //如果有变动,登记事件

                       }

                   }

               }

           }

    后面的方法完全一样了。

    最后,我们来看看整个模拟代码:

    publicstringAspWebService(string url,byte[] Content)

    {

          //1.根据是否有附加信息,设置IsPostBack

           bool IsPostBack=(Content ==null)?false:true;

                 //2.根据aspx页面布局,创建页面及控件

           string strAspxLayout ="......<asp:ButtonID='Button1' runat='server' Text='Button' />....";

           if (strAspxLayout.Length > 0)

           {

               Button btn =newButton();

               btn.ID ="Button1";

               btn.Text ="Button";

           }

                 //3.调用Page_init方法

           Page_Init(this,newEventArgs());

                 //4.如果是页面回传,根据Content恢复客户端页面,根据ViewState恢复上一次页面,并比较,然后登记事件

           if (IsPostBack)

           {

               Page page_Cur =this;

               this.TextBox1.Text ="new value";

     

               Page page_Last =newPage();

               string strViewState ="内容从Content中提取隐藏域";

               if (strViewState.Length > 0)

               {

                   //根据strViewState创建控件并添加到page_Last中

               }

     

               foreach (Controlctlin page_Cur.Form.Controls)

               {

                   foreach(Control ctl2inpage_Last.Controls)

                   {

                       if (ctl.ID == ctl2.ID)

                       {

                           //如果有变动,登记事件

                       }

                   }

               }

           }

    //5.调用Page_Load方法

           Page_Load(this,newEventArgs());

    //6.根据前面登记的事件,调用对应的方法

           Button1_Click(this,newEventArgs());

                 //7.调用Render方法完成HTML字符串,HTML包含了ViewState

           string strHTML = Render();

           return strHTML;

    }

     

    下面,我们来讨论几个相关问题:

    1.       数据绑定方式如下,为什么一点分页按钮,就没有数据了?

    protectedvoid Page_Load(object sender,EventArgse)

       {

           if (!IsPostBack)

           {

               GridView1.DataSource =……;

               GridView1.DataBind();

           }

       }

    分析:前面说了,每次页面都是全新的,当页面回传时,IsPostBack为true,上面的代码就不会绑定数据了,当然是空的。

    解决方法:把if(!IsPostBack)去掉。

    2.       动态添加控件是不是在Page_Load方法里面?

    分析:在页面的生命周期中,按照这样的先后顺序处理:读取ASPX中的控件、创建内存控件、调用Page_Init、恢复客户端控件和ViewState、调用Page_Load,显然,最合适的地方是Page_Init方法中,这样,和“原生”的控件没什么区别了。

    3.       能否查看、修改发送给客户端的HTML

    分析:显然,只需重载一下Render方法。

       protectedoverridevoidRender(HtmlTextWriter writer)

       {

           StringWriter sw =newStringWriter();

           HtmlTextWriter htmlw =newHtmlTextWriter(sw);

           base.Render(htmlw);

           htmlw.Flush();

           htmlw.Close();

    //这就是发送给客户端的内容,你可以随便加工,比如去掉多余的空格等等

           string strConn = sw.ToString();

                 //这句话别忘了

           Response.Write(strConn);

       }

    4.       能否减小ViewState以节约带宽

    我们知道,ViewState保存了本次页面的信息,附加到html中,页面回传时,再由服务器处理(根据ViewState还原上一次页面的信息),仅仅用于服务器端,而客户端仅仅起临时保存用,但发送到客户端再回传到服务器,一个来回两次占用带宽,的确是一笔不小的开支,尤其是当控件很多时,甚至占到整个HTML内容的1/3以上,既然ViewState仅仅是用于服务器端,那么我们可以想办法让ViewState留在服务器上,具体思路是:

    (1)在数据库中创建一个两列的表,一列是主键ID,一个列存放ViewState内容,ViewState生成以后(假如为string strViewState),我们拦截下来,用一个新的ID,把strViewState保存到数据库中,并用该ID替换strViewState返回,这样一来,HTML中的ViewState内容并不是真正的ViewState,而是刚刚生成的ID,体积当然非常小了。

    (2)页面回传后,把客户端传回来的ViewState取出来,显然,这时得到的是第(1)步存放的ID而不是真正的ViewState,我们根据ID,从数据看里面查询出真正的ViewState,同时,顺便把ID对应的记录删除。

    这样一来,就成功将真正的ViewState留在了服务器,不过,需要占用一定的服务器空间,也要记得清理过期的数据(只有页面回传时,上一次的数据才会被删除),这就是所谓的用空间换时间(用服务器空间换取网络传送时间),有人可能会说,服务器读写ViewState也是要时间的,但别忘了,IIS和数据库一般是同一台服务器,即使是多台,也在一个局域网内,速度很快,这个速度远远快于通过http协议传送页面内容,实现的关键步骤如下:

     

       ///<summary>

       ///将试图持久化到数据库中

       ///</summary>

       ///<paramname="state">本页面ViewState</param>

       protectedoverridevoidSavePageStateToPersistenceMedium(object state)

       {

           //1.获取一个ID

           string strViewState =……;

           //2.将ID和state保存到服务器,注意state可以序列化成byte[],方法略

                 SaveState(strViewState, state);

           //3.用ID替换到真正的ViewState

           base.SavePageStateToPersistenceMedium(strViewState);

       }

       ///<summary>

       ///载入通过数据库持久化的视图

       ///</summary>

       ///<returns>真正的ViewState</returns>

       protectedoverrideobjectLoadPageStateFromPersistenceMedium()

       {

           //1.从ViewState中提取ID

           string strViewState = (string)((Pair)base.LoadPageStateFromPersistenceMedium()).Second;

           //2.根据ID查询真正的ViewState,同时将原来的从数据库删除,具体方法略

    byte [] states=…..;

           //3.返回真正的ViewState

           return states;

       }

    5.       Ajax貌似可以前台后台同时存在

    Ajax访问页面和普通访问差别不是很大,关键是页面返回信息的处理方式不一样,普通页面信息返回后,浏览器把老的页面信息扔掉,显示新返回来的页面信息,而通过Ajax得到新的信息后,并不是由浏览器直接替换老的页面,而是交给JavaScript的一个函数(也就是回调函数),该函数根据返回的信息,做一些处理,然后对原来的页面进行有选择性的操作,就是所谓的“局部更新”,或者说,JavaScript回调函数取得了返回的字符串,然后根据该字符串做自己想做的事情,也可以什么都不做,直接扔掉。既然是通过回调函数处理得到的字符串,那么,字符串就不一定必须是HTML了,可以是任何字符串,回调函数收到后,可以进行任何处理,而没必要一定去新某个HTML控件,比如我们在服务器端这么处理:

    protectedoverridevoid Render(HtmlTextWriterwriter)

       {

           string strName ="";

           switch (Request["WorkNo"])

           {

               case"1001":

                   strName ="张三";

                   break;

               case"1002":

                   strName ="李四";

                   break;

           }   

     

           Response.Write(strName);

       }

    显然,重载了Render方法,根据工号返回对应的姓名,客户端收到以后,可以简单用alert(text)来提示一下,而页面内容不会改变,是不是有点WebService的味道?我们知道,网站可以压缩(主要针对页面、脚本等),而WebService返回的信息难以压缩,因此,需要返回大量数据时,用aspx页面代替WebService也是不错的思路。

  • 相关阅读:
    6 原型模式
    10 观察者模式
    4 代理模式
    写错误日志
    C#事件的使用
    将int型数字转换成7位字符串,不足的时候,前面补0
    Excel 2010导数据到SQL SERVER 2008
    jquery checkbox
    修改注册表开启IE跨域访问功能
    存储过程一例
  • 原文地址:https://www.cnblogs.com/azzhang/p/4215205.html
Copyright © 2020-2023  润新知