ASP.NET AJAX(14)__UpdatePanel与服务器端脚本控件脚本控件的作用脚本控件的指责Extender模型脚本控件和Extender模型在PostBack中保持状态在UpdatePa

时间:2022-05-03
本文章向大家介绍ASP.NET AJAX(14)__UpdatePanel与服务器端脚本控件脚本控件的作用脚本控件的指责Extender模型脚本控件和Extender模型在PostBack中保持状态在UpdatePa,主要内容包括脚本控件的作用、脚本控件的指责、Extender模型、脚本控件和Extender模型、在PostBack中保持状态、在UpdatePanel中使用内联脚本、内联脚本、基本概念、基础应用、原理机制和需要注意的事项等,并结合实例形式分析了其使用技巧,希望通过本文能帮助到大家理解应用这部分内容。

脚本控件的作用

ASP.NET AJAX的脚本控件,连接了服务器端和客户端,因为我们(可以)只在服务器端编程,而效果产生在客户端,这就需要我们首先在服务器端编写一个控件类,然后包含一个或几个脚本文件,其中定义了客户端组件,可以让开发人员只在服务端操作控件,而在页面上添加客户端行为

一个典型的脚本控件就是UpdateProgress,我们来看一下它的实现方式

一个UpdateProgress的简单示例

创建一个aspx页面

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="UpdateProgress.aspx.cs" Inherits="Demo13_UpdateProgress" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
        <asp:ScriptManager ID="ScriptManager1" runat="server">
        </asp:ScriptManager>
        
        <asp:UpdatePanel ID="UpdatePanel1" runat="server">
            <ContentTemplate>
                <%= DateTime.Now %><br />
                <asp:Button ID="Button1" runat="server" Text="Update" onclick="Button1_Click" />
            </ContentTemplate>
        </asp:UpdatePanel>
        
        <asp:UpdateProgress ID="UpdateProgress1" runat="server" DisplayAfter="1000">
            <ProgressTemplate>
                Loading......
            </ProgressTemplate>
        </asp:UpdateProgress>
    </form>
</body>
</html>

在Button1的点击事件里,让线程等待三秒钟

protected void Button1_Click(object sender, EventArgs e)
    {
        System.Threading.Thread.Sleep(3000);
    }

开发页面,我们点击按钮Button1,可以看到,等待一秒钟后,出现“Loading…”字样,因为我们设置了UpdateProfress的DisplayAfter为1000,这里代码1000毫秒,而我们让控件的点击事件触发,引发异步回送后,在服务器端停留了三秒钟,所以三秒后,时间更新,同时“Loading…”字样消失

我们打开在网页中右键选择打开源文件,可在页面的form结束之前找到如下代码

Sys.Application.add_init(function() {
    $create(Sys.UI._UpdateProgress, {"associatedUpdatePanelId":null,"displayAfter":1000,"dynamicLayout":true}, null, null, $get("UpdateProgress1"));
});

这段代码,是不是很熟悉呢?没错,如果看过我上一节的文章的,就会很熟悉这种代码格式,它响应了Application的init事件,然后创建一个Sys.UI._UpdateProgress类型的组件,然后设置它绑定的ID,这里是Null,和displayAfter,停留多少毫秒后显示,和UpdateProgress的占位方式,最后,设置的是它要修饰的element

脚本控件的指责

  • 在页面上引入客户端组件所需要的脚本文件
  • 在页面上生成使用客户端组件的脚本代码

于是出现了IScriptControl接口

  • IEnumerable<ScriptReference> GetScriptReferences()方法:描述页面中需要加载在页面中的脚本文件
  • IEnumerable<ScriptDescriptor> GetScriptDescriptors()方法:告诉页面需要输出的脚本内容

如果我们要开发一个脚本控件,除了实现以上的两个方法以外,还需要重写Control类的两个方法

  • OnPreRender
  • OnRender

由于大部分的脚本控件对于以上两个方法实现相同,因此在开发时候,也可以直接继承ScriptControl类,它已经实现了IScriptControl接口

一个脚本控件的示例:StyledTextBox

创建一个名为StyledTextBox.js的文件

/// <reference name="MicrosoftAjax.js"/>

Type.registerNamespace('Demo');//注册命名控件

Demo.StyledTextBox = function(element) {
    Demo.StyledTextBox.initializeBase(this, [element]);//调用父类构造函数

    this._highlightCssClass = null;
    this._nohighlightCssClass = null;

    this._onfocusHandler = null;
    this._onblurHandler = null;
}
Demo.StyledTextBox.prototype =
{
    //highlightCssClass属性
    get_highlightCssClass: function() {
        return this._highlightCssClass;
    },
    set_highlightCssClass: function(value) {
        if (this._highlightCssClass !== value) {
            this._highlightCssClass = value;
            this.raisePropertyChanged('highlightCssClass');
        }
    },


    //nohighlightCssClass属性
    get_nohighlightCssClass: function() {
        return this._nohighlightCssClass;
    },
    set_nohighlightCssClass: function(value) {
        if (this._nohighlightCssClass !== value) {
            this._nohighlightCssClass = value;
            this.raisePropertyChanged('nohighlightCssClass');
        }
    },

    initialize: function() {
        Demo.StyledTextBox.callBaseMethod(this, 'initialize');
        
        //创建两个EventHandler
        this._onfocusHandler = Function.createDelegate(this, this._onFocus);
        this._onblurHandler = Function.createDelegate(this, this._onBlur);
        
        //把这两个EventHandler加到我们要修饰的控件上
        $addHandlers(this.get_element(),
            { 'focus': this._onFocus, 'blur': this._onBlur },
            this);

        this.get_element().className = this._nohighlightCssClass;
    },

    dispose: function() {
        $clearHandlers(this.get_element());

        Demo.StyledTextBox.callBaseMethod(this, 'dispose');
    },

    //如果获得焦点,把highlightCssClass给到这个element的class上
    _onFocus: function(e) {
        if (this.get_element() && !this.get_element().disabled) {
            this.get_element().className = this._highlightCssClass;
        }
    },
    
    //如果失去焦点,把nohighlightCssClass给到这个element的class上
    _onBlur: function(e) {
        if (this.get_element() && !this.get_element().disabled) {
            this.get_element().className = this._nohighlightCssClass;
        }
    }
}

Demo.StyledTextBox.registerClass('Demo.StyledTextBox', Sys.UI.Control);

然后创建一个aspx页面

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="ScriptControl.aspx.cs" Inherits="Demo13_ScriptControl" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
    <style type="text/css">
        .NoHighLight
        {
            border:solid 1px gray;
             background-color:#EEEEEE;
        }
        .HighLight
        {
            border:solid 1px gray;
            background-color:Ivory;
        }
    </style>
</head>
<body>
    <form id="form1" runat="server">
        <asp:ScriptManager ID="ScriptManager1" runat="server">
            <Scripts>
                <asp:ScriptReference Path="~/Demo13/StyledTextBox.js" />
            </Scripts>
        </asp:ScriptManager>
        
        <input type="text" id="textBox" />
        
        <script language="javascript" type="text/javascript">
            Sys.Application.add_init(function() {
                $create(
                    Demo.StyledTextBox,
                    {
                        highlightCssClass: "HighLight",
                        nohighlightCssClass: "NoHighLight"
                    },
                    null,
                    null,
                    $get("textBox"));
            });
        </script>
    </form>
</body>
</html>

代码很简单,和上一讲的如出一辙。。。什么如出一辙,本来就是一回事,文本框获得焦点,样式设置为HighLight,失去焦点,样式设置为NoHighLight。

这里,我们还是在客户端进行编程的,还没有做到在服务端编写在客户端生效的这样一个效果

我们开始做一个服务端控件

创建一个名为StyledTextBox.cs的类

using System;
using System.Data;
using System.Configuration;
using System.Linq;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Xml.Linq;
using System.Collections.Generic;

namespace Demo
{
    /// <summary>
    ///StyledTextBox 的摘要说明
    /// </summary>
    //继承自TextBox,实现接口IScriptControl
    public class StyledTextBox : TextBox, IScriptControl
    {
        //两个属性,分别是控件或者焦点和失去焦点时候要设置的样式
        public string HighlightCssClass { get; set; }
        public string NoHighlightCssClass { get; set; }


        #region IScriptControl 成员

        //告诉ScriptManager将如何生成脚本代码
        public IEnumerable<ScriptDescriptor> GetScriptDescriptors()
        {
            ScriptControlDescriptor descriptor = new ScriptControlDescriptor("Demo.StyledTextBox", this.ClientID);//参数1:创建的组件类型,参数2:返回此控件在客户端生成的ID
            //添加两个属性
            descriptor.AddProperty("highlightCssClass", this.HighlightCssClass);
            descriptor.AddProperty("nohighlightCssClass", this.NoHighlightCssClass);

            yield return descriptor;
        }

        //告诉页面我们要引入的脚本文件
        public IEnumerable<ScriptReference> GetScriptReferences()
        {
            ScriptReference reference = new ScriptReference();
            reference.Path = this.ResolveClientUrl("~/Demo13/StyledTextBox.js");//ResolveClientUrl方法用于浏览器的指定资源的完全限定 URL

            yield return reference;
        }

        #endregion

        //以下是开发一个脚本控件,需要重写Control的两个方法

        protected override void OnPreRender(EventArgs e)
        {
            //如果不在设计期间
            if (!this.DesignMode)
            {
                //把自身注册给ScriptManager的ScriptControl
                ScriptManager.GetCurrent(this.Page).RegisterScriptControl<StyledTextBox>(this);
            }

            base.OnPreRender(e);
        }

        protected override void Render(HtmlTextWriter writer)
        {
            if (!this.DesignMode)
            {
                //把自身注册给ScriptManager的ScriptDescriptors
                ScriptManager.GetCurrent(this.Page).RegisterScriptDescriptors(this);
            }
            base.Render(writer);
        }
    }
}

然后修改刚才的aspx页面

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="ScriptControl.aspx.cs" Inherits="Demo13_ScriptControl" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<%@ Register Namespace="Demo" TagPrefix="demo" %>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
    <style type="text/css">
        .NoHighLight
        {
            border:solid 1px gray;
             background-color:#EEEEEE;
        }
        .HighLight
        {
            border:solid 1px gray;
            background-color:Ivory;
        }
    </style>
</head>
<body>
    <form id="form1" runat="server">       
        <input type="text" id="textBox" />
        <asp:ScriptManager runat="server" ID="s"></asp:ScriptManager>       
        <demo:StyledTextBox ID="StyledTextBox1" runat="server" HighlightCssClass="HighLight" NoHighlightCssClass="NoHighLight" />       
    </form>
</body>
</html>

注意,这里我们不需要javascript代码,也不需要在页面中引入我们刚才的js文件,只需要在页面中注册这个脚本控件,然后在页面中当作服务端控件那样直接使用,设置属性就可以啦

我们看到StyledTextBox继承了TextBox,同时扩展了TextBox,这个概念和客户端组件的Control模型很相似,事实上普通的脚本控件包含的脚本中大多数都是定义了客户端的Control模型的组件

Extender模型

和客户端的Behavior模型概念类似的服务端模型是Extender模型,可以为一个服务器端控件附加多个Extender,Extender模型理论上继承自IExtenderControl即可,实际上开发时候议案继承自ExtenderControl类,免去一些额外的工作

开发ExtenderControl需要覆盖一下两个方法

  • IEnumerable<ScriptReference> GetScriptReferences()方法:描述页面中需要加载在页面中的脚本文件
  • IEnumerable<ScriptDescriptor> GetScriptDescriptors(Control targetControl)方法:需要在目标控件的执行的脚本代码
一个扩展控件的示例:FocusExtender

新建一个类库项目,添加引用System.Web和System.Web.Extensions

创建一个名为FocusBehavior.js的文件

Type.registerNamespace('Demo');

Demo.FocusBehavior = function(element) {
    Demo.FocusBehavior.initializeBase(this, [element]);

    this._highlightCssClass = null;
    this._nohighlightCssClass = null;
}
Demo.FocusBehavior.prototype =
{
    get_highlightCssClass: function() {
        return this._highlightCssClass;
    },

    set_highlightCssClass: function(value) {
        if (this._highlightCssClass !== value) {
            this._highlightCssClass = value;
            this.raisePropertyChanged('highlightCssClass');
        }
    },

    get_nohighlightCssClass: function() {
        return this._nohighlightCssClass;
    },

    set_nohighlightCssClass: function(value) {
        if (this._nohighlightCssClass !== value) {
            this._nohighlightCssClass = value;
            this.raisePropertyChanged('nohighlightCssClass');
        }
    },

    initialize: function() {
        Demo.FocusBehavior.callBaseMethod(this, 'initialize');

        $addHandlers(this.get_element(),
            { 'focus': this._onFocus, 'blur': this._onBlur },
            this);

        this.get_element().className = this._nohighlightCssClass;
    },

    dispose: function() {
        $clearHandlers(this.get_element());

        Demo.FocusBehavior.callBaseMethod(this, 'dispose');
    },

    _onFocus: function(e) {
        if (this.get_element() && !this.get_element().disabled) {
            this.get_element().className = this._highlightCssClass;
        }
    },

    _onBlur: function(e) {
        if (this.get_element() && !this.get_element().disabled) {
            this.get_element().className = this._nohighlightCssClass;
        }
    }
}
Demo.FocusBehavior.registerClass('Demo.FocusBehavior', Sys.UI.Behavior);

代码和之前的一样,我就没添加注释,看过我之前的文章,看这段代码不是问题

然后创建一个名为FocusExtender.cs的类文件

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.UI;
//描述我们需要引用的资源
[assembly: WebResource("FocusExtender.FocusBehavior.js", "application/x-javascript")]
namespace FocusExtender
{
    [TargetControlType(typeof(Control))]
    public class FocusExtender : ExtenderControl
    {
        public string HighlightCssClass { get; set; }
        public string NoHighlightCssClass { get; set; }


        protected override IEnumerable<ScriptDescriptor> GetScriptDescriptors(Control targetControl)
        {
            ScriptBehaviorDescriptor descriptor = new ScriptBehaviorDescriptor("Demo.FocusBehavior", targetControl.ClientID);
            descriptor.AddProperty("highlightCssClass", this.HighlightCssClass);
            descriptor.AddProperty("nohighlightCssClass", this.NoHighlightCssClass);

            yield return descriptor;
        }

        protected override IEnumerable<ScriptReference> GetScriptReferences()
        {
            ScriptReference reference = new ScriptReference();
            reference.Assembly = "FocusExtender";
            reference.Name = "FocusExtender.FocusBehavior.js";

            yield return reference;
        }
    }
}

在这里描述应用资源的时候应该注意,这里不是文件名,也不是这个类库的名称加点然后加文件名

我们点击项目右键属性,打开属性页面

我们的资源名称,是默认命名控件.文件名称

这里的代码,与前面的示例唯一不同的是,多了一个targetControl,在类名前加一个标识,表示我们这个控件作用到那种类型的控件上,我们这里设置为“Control”,表示所有控件

还应该注意一点,我们应该在项目生成操作的时候,把js文件作为嵌入的资源,点击js文件属性,然后在属性对话框里做相应修改

然后我们就可以在我们的网站里使用它啦

在网站中点击右键添加引用,选择我们创建的FocusExtender项目,会在bin目录下出现一个FocusExtender.dll,注意要先生成一下

创建aspx页面

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="FocusExtender.aspx.cs" Inherits="Demo13_FocusExtender" %>
<%@ Register Assembly="FocusExtender" Namespace="FocusExtender" TagPrefix="demo" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
    <style type="text/css">
        .NoHighLight
        {
            border:solid 1px gray;
             background-color:#EEEEEE;
        }
        .HighLight
        {
            border:solid 1px gray;
            background-color:Ivory;
        }
    </style>
</head>
<body>
    <form id="form1" runat="server">
        <asp:ScriptManager ID="ScriptManager1" runat="server">
        </asp:ScriptManager>
        
        <asp:TextBox runat="server" ID="textBox" />
        
        <demo:FocusExtender ID="FocusExtender1" runat="server" TargetControlID="textBox" HighlightCssClass="HighLight" NoHighlightCssClass="NoHighLight" />
        
        <asp:Panel Width="200" Height="200" ID="panel" runat="server">Hello World!</asp:Panel>
        
        <demo:FocusExtender ID="FocusExtender2" runat="server" TargetControlID="panel" HighlightCssClass="HighLight" NoHighlightCssClass="NoHighLight" />
    </form>
</body>
</html>

这样,我们把我们创建的控件“附加”到了一个文本框和一个Panel上,在同时我们提供了三个属性,作用的控件,和两个样式属性,运行页面,得到与前面我们的脚本控件相同的效果

脚本控件和Extender模型

  • IScriptControl:对应Sys.Component__ScriptComponentDescriptor
  • ScriptComtrol:对应Sys.UI.Control__ScriptControlDescript
  • ExtenderControl:对应Sys.UI.Behavior__ScriptBehaviroDescriptor

在PostBack中保持状态

  • 与普通服务器控件不同,ScriptControl的精髓在客户端,在普通的服务端控件中使用ViewSate并,它不能保持客户端状态
  • 组件状态可能在客户端被改变
  • 需要在PostBack前后保持客户端状态

在异步刷新中,由于不刷新整个页面,因此可以保存在页面变量中,但是完整的PostBack需要将状态从客户端提交到服务器端,然后再写回给客户端,客户端向服务器端提交信息的方法有以下三种

  • Query String(改变URL)
  • Cookie(作用域太大)
  • Input+Post

那么,如果我们要保存页面的某个状态,就分两种情况啦

一种是异步刷新,因为异步刷新的时候,页面并没有销毁,所以,我们可以把保存这种状态的键值放在window对象或者一个HiddenField中,但是如果是传统的更新,页面是会被销毁的,则只能保存在HiddenField中啦

在UpdatePanel中使用内联脚本

  • UpdatePanel在更新时使用的是设置innerHTML的做法
  • 设置innerHTML并不会执行其中的内联脚本
  • 需要把内联脚本提出来,然后eval

为了让UpdatePanle可以使用内联脚本,就需要使用一个内联脚本控件

内联脚本

  • 要子啊异步更新后执行脚本,唯一的方法就是调用ScriptManager的脚本注册方法
  • 开发一个控件,在普通加载时简单输出内联脚本,在异步更新时调用脚本注册方法
一个内联脚本的示例

创建一个aspx页面

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="InlineScripts.aspx.cs" Inherits="Demo13_InlineScripts" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
        <asp:ScriptManager runat="server" ID="sm" />
        
        <asp:UpdatePanel runat="server" ID="update">
            <ContentTemplate>                
                <%= DateTime.Now %>
                <asp:Button runat="server" ID="btnRefresh" Text="Refresh" />
                
                    <script language="javascript" type="text/javascript">
                        alert("Xiaoyaojian");
                    </script>
            </ContentTemplate>
        </asp:UpdatePanel>
    </form>
</body>
</html>

打开页面,刷新页面,都会弹出提示框,而在我们点击Refresh后,脚本却并没有被执行,这不是我们想要的效果,但是这里的脚本在异步回送的时候确实是被加载啦,那要怎么做呢  。。。。。

我们创建一个名为InlineScript的类库项目,添加一个类

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.UI;
using System.IO;

namespace InlineScript
{
    public class InlineScript : Control
    {
        //重写Render方法,每次UpdatePanle更新,这个方法都会被调用
        protected override void Render(HtmlTextWriter writer)
        {
            ScriptManager sm = ScriptManager.GetCurrent(this.Page);

            if (sm.IsInAsyncPostBack)//如果页面是异步更新的情况下
            {
                StringBuilder sb = new StringBuilder();
                base.Render(new HtmlTextWriter(new StringWriter(sb)));
                //得到的UpdatePanel中的script标签的所有内容
                string script = sb.ToString();
                ScriptManager.RegisterStartupScript(this, typeof(InlineScript), this.UniqueID, script, false);//把这段脚本注册到页面上
            }
            else
            {
                base.Render(writer);
            }
        }
    }
}

生成项目,然后和上面一样,在网站项目中添加对这个项目的引用,然后修改上面的页面

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="InlineScripts.aspx.cs" Inherits="Demo13_InlineScripts" %>
<%@ Register Assembly="InlineScript" Namespace="InlineScript" TagPrefix="demo" %><%--注册这个控件--%>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
        <asp:ScriptManager runat="server" ID="sm" />
        
        <asp:UpdatePanel runat="server" ID="update">
            <ContentTemplate>
                <%= DateTime.Now %>
                <asp:Button runat="server" ID="btnRefresh" Text="Refresh" />
                <%--使用注册的控件--%>
                <demo:InlineScript runat="server">

                    <script language="javascript" type="text/javascript">
                        alert("Xiaoyaojian");
                    </script>

                </demo:InlineScript>
            </ContentTemplate>
        </asp:UpdatePanel>
    </form>
</body>
</html>

打开页面,刷新,点击按钮,都会弹出提示框,对嘛   这才是我们要的效果