zoukankan      html  css  js  c++  java
  • JavaScript And Ajax(在客户端回调中使用 Ajax)

           采用 Ajax 的方式,可以创建令人印象深刻、高度响应的网页。然而,编写客户端代码非常耗时。VS 不能为之提供丰富的设计体验,也没有调试工具追踪那些松散的 js 语言中不可避免的错误;甚至你成功完成了工作,还要在各种浏览器上进行测试,除非你非常熟悉各种浏览器对 js 支持的微小差别。

           由于这些原因,许多开发人员不手工编写客户端脚本,甚至在设计 Ajax 风格的页面时也是如此。相反,他们更乐意使用能够生成他们需要的脚本的高级组件。

           一个例子是免费的第三方 Ajax.NET 库(可从 http://ajax.schwarz-interactive.de/csharpsample 获得)。Ajax.NET 使用特性标记方法,然后这些方法就可以通过客户端回调和自定义 HTTP 处理程序远程调用。

           另一个例子是 ASP.NET AJAX,它是更加全面的 Ajax 工具集,以后会介绍。

           虽然两者都是很好的选择,你还能够执行最核心的 Ajax 任务(发送异步请求到服务器),使用 ASP.NET 中更加直观的客户端回调功能。客户端回调让你能刷新页面的部分数据而不需要触发完整的回传。最妙的是,你不需要使用 XMLHttpRequest 对象的脚本代码,不过,仍然需要编写处理服务器端响应的客户端脚本

    创建客户端回调

           要在 ASP.NET 里创建客户端回调,首先要计划如何让通信正常工作。这里是最基本的模型:

    1. 在某一时刻,js 事件发生,触发服务器回调。
    2. 此时,正常的页面生命周期开始,所有正常的服务器端事件发生。
    3. 这个过程完成时,且页面正确初始化后,ASP.NET 执行服务器端回调方法。此方法具有固定的签名:接收并返回一个字符串
    4. 页面从服务器端方法接收到响应后,使用 js 代码相应的修改网页。

           ASP.NET 架构用于抽象出通信过程,这样你可以构建使用回调的页面而不用考虑底层逻辑,就像你从视图状态以及页面生命周期得到的便利一样。

           这个例子,你将会看到有两个下拉列表的页面。第一个填充来自 Northwind 数据库的一系列区域,它在页面第一次加载时完成。第二个列表开始为空,直到用户在第一个列表中作出选择,此时第二个列表的内容通过回调获得并被插入到列表里。

    1. 构建基本页面

           填充第一个列表很容易,声明性的绑定到数据源控件:

    image

    <div style="font-family: Verdana; font-size: small">
        Choose a Region, and then a Territory:<br />
        <br />
        <asp:DropDownList ID="lstRegions" runat="server" Width="210px" DataSourceID="sourceRegions"
            DataTextField="RegionDescription" DataValueField="RegionID">
        </asp:DropDownList>
        <asp:DropDownList ID="lstTerritories" runat="server" Width="275px">
        </asp:DropDownList>
        <br />
        <br />
        <br />
        <asp:Button ID="cmdOK" runat="server" Text="OK" Width="50px" OnClick="cmdOK_Click" />
        <br />
        <br />
        <asp:Label ID="lblInfo" runat="server"></asp:Label>
        <asp:SqlDataSource ID="sourceRegions" runat="server" ConnectionString="<%$ ConnectionStrings:Northwind %>"
            SelectCommand="SELECT 0 As RegionID, '' AS RegionDescription UNION SELECT RegionID, RegionDescription FROM Region">
        </asp:SqlDataSource>
    </div>

    2. 实现回调

           要接收一个回调,需要实现 ICallbackEventHandler 接口的类。如果你知道这个回调会用到多个页面,为它创建专门的类是有意义的。如果是只为单一页面实现的功能,可以在这个网页里直接实现 ICallbackEventHandler:

    public partial class ClientCallback : System.Web.UI.Page, ICallbackEventHandler
    { … }

           ICallbackEventHandler 接口定义了 2 个方法:

    • RaiseCallbackEvent() :以字符串参数的形式得到浏览器的事件数据。它首先被触发。
    • GetCallbackResult() :它紧接着被触发,它把结果返回给页面。

           ASP.NET 客户端回调的主要限制是它强制你使用单个字符串传送数据。如果要传送较复杂的信息,必须设计一个方法把它序列化为一个字符串,然后在客户端反序列化它。

    private string eventArgument;
    public void RaiseCallbackEvent(string eventArgument)
    {
        this.eventArgument = eventArgument;
    }
     
    public string GetCallbackResult()
    {
        SqlConnection con = new SqlConnection(
            WebConfigurationManager.ConnectionStrings["Northwind"].ConnectionString);
        SqlCommand cmd = new SqlCommand(
            "SELECT * FROM Territories WHERE RegionID=@RegionID", con);
        cmd.Parameters.Add(new SqlParameter("@RegionID", SqlDbType.Int, 4));
        cmd.Parameters["@RegionID"].Value = Int32.Parse(eventArgument);
     
        StringBuilder results = new StringBuilder();
        try
        {
            con.Open();
            SqlDataReader reader = cmd.ExecuteReader();
     
            while (reader.Read())
            {
                results.Append(reader["TerritoryDescription"]);
                results.Append("|");
                results.Append(reader["TerritoryID"]);
                results.Append("||");
            }
            reader.Close();
        }
        catch (SqlException err)
        {
            // Hide errors.
        }
        finally
        {
            con.Close();
        }
        return results.ToString();
    }

           在这个例子中,不能使用声明性的数据绑定。因为回调方法不能直接访问页面上的控件!和回传不同,调用 RaiseCallbackEvent() 时,页面还没执行重建过程。相反,RaiseCallbackEvent() 方法只是被外部调用以请求一些额外的信息。这些问题由回调方法自己解决。

           因为结果要以一个字符串返回(此字符串还要在 js 代码中执行逆向工程),代码有点繁琐。一个管道符号分隔2个字段,2个管道符号表示新行的开始。显然,这种方式有点脆弱,只要地区记录包含管道符号,它就会带来明显的问题

    3. 编写客户端脚本

           客户端脚本涉及到服务器和客户端的数据交互。服务器端需要一个方法准备结果,客户端也需要一个方法接收并处理结果

           这个方法名称可以任意,但必须接收 2 个参数,如下:

    function ClientCallback(result, context) { …}

           结果参数含有序列化的字符串。对于本例,由客户端脚本解析这个字符串并填入相应的列表框:

    <script type="text/javascript">
        function ClientCallback(result, context) {
            var lstTerritories = document.getElementById("lstTerritories");
     
            lstTerritories.innerHTML = "";
            var rows = result.split("||");
            for (var i = 0; i < rows.length - 1; ++i) {
                var fields = rows[i].split("|");
                var territoryDesc = fields[0];
                var territoryID = fields[1];
                var option = document.createElement("option");
     
                option.value = territoryID;
                option.innerHTML = territoryDesc;
                lstTerritories.appendChild(option);
            }
        }
    </script>

           还缺少一个细节。虽然已经定义了两端的消息交互,但还没有把它们真正的联到一起。你需要一个客户端触发器来调用回调。对于这个例子,可以响应地区列表的 onchange 事件:

    protected void Page_Load(object sender, EventArgs e)
    {

        lstRegions.Attributes["onChange"] = callbackRef;

        ……
    }

           callbackRef 是调用回调的 js 代码。不过,你究竟要怎么编写这段代码呢?ASP.NET 提供了一个方便的 GetCallbackEventReference() 方法,它能构建你需要的回调引用:

    protected void Page_Load(object sender, EventArgs e)
    {
        string callbackRef = Page.ClientScript.GetCallbackEventReference(
            this, "document.getElementById('lstRegions').value", "ClientCallback", "null", true);
        lstRegions.Attributes["onChange"] = callbackRef;    
    }

           参数1:处理回调的 ICallbackEventHandler 对象的引用

           参数2:客户端向服务器端传递的信息(一个字符串)。

           参数3:从服务器回调获取结果的客户端 js 的函数名称。

           参数4:要传给客户端函数的上下文信息。如果同一个 js 函数处理多个回调并且要区分它们时,这个参数很有用

           参数5:是否异步执行回调。应一直为 true;避免发生网络问题时页面被锁定。

    4. 禁用事件验证

           POST 注入攻击是恶意用户修改发送到服务器的 HTTP POST 请求,使之包含在相应控件中没用的值的攻击。例如,用户可能把发送的参数修改为不在列表中的列表选项值,如果不检查这样的值,代码可能会泄漏敏感数据。

           ASP.NET 使用事件验证避免 POST 注入攻击。事件验证验证所有提交的数据必须在 ASP.NET 执行页面生命周期之前就已经存在。遗憾的是,事件验证常会在 Ajax 风格的页面中产生问题。对于此例,项动态的添加到地区列表。用户选定一个区域并回传到页面时,ASP.NET 将会引发“无效回发或回调参数”的错误,因为选中的区域没有定义到服务器端控件里。

    image

           事件验证 不是所有控件都支持的功能。只有那些使用了 SupportsEventValidaion 特性的控件类才实现该功能。在 ASP.NET 里,大多依赖回传数据的控件使用该特性(如 ListBox、DropDownList、CheckBox、TreeView、Calendar 等),那些不限制允许值的控件例外,。例如,TextBox 不使用事件验证,因为允许用户在其中输入任意值。

           有 2 个办法可以解决事件验证的问题。

           最安全的办法是显式告诉 ASP.NET 控件允许的额外值(使用叫做 _EVENTVALIDATION 的隐藏输入标签跟踪允许的值)。遗憾的是,这种方法单调乏味,甚至有时不切实际!要使用这种方法必须为每个可能的值调用 Page.ClientScript.RegisterForEventValidation() 方法,必须覆盖 Page.Render() 方法在呈现阶段完成这个任务。

           下面这个例子允许用户在 lstTerritories 控件中选择 TerritoryID 为 10 的区域:

    protected override void Render(HtmlTextWriter writer)
    {
        Page.ClientScript.RegisterForEventValidation(lstRegions.UniqueID, "10");
        base.Render(writer);
    }

           一个明显的问题是,很多情况下你不知道所有可能的值。它们可能是动态产生的或者来自其他数据源(如 Web 服务)。对于本例,你需要从数据库获取所有 TerritoryID 值,遍历并注册每个值。它不仅带来了额外的工作,如果页面出现后加入了更多区域它还会带来其他问题!

           唯一理想的解决方案是禁用事件验证。遗憾的是,不能为单一的控件禁用事件验证。必须使用 Page 指令的 EnableEventValidation 属性为整个页面把它关闭:

    <%@ Page EnableEventValidation="false" ... %>

           也可设置在配置文件中禁用整个网站的事件验证。然而,不推荐使用这一种方式,会为其他页面带来安全风险

           要用代码获得选中的地区,不能使用 lstTerritories 控件。因为 lstTerritories 控件是服务器端版本的列表,所以它不包括动态添加的值。相反,要直接从 Request.Forms 集合中获取选定的值:

    protected void cmdOK_Click(object sender, EventArgs e)
    {
        // The server-side control doesn't have the territory list, so
        // you need to get the selected territory from the Request object.
        // Remember to check for injection attacks if the Territories
        // table contains sensitive data.
        lblInfo.Text = "You selected territory ID #" + Request.Form["lstTerritories"];
     
        // Reset the region list box (because the territory list box will be empty).
        lstRegions.SelectedIndex = 0;
    }

           现在测试整个页面的效果,如下:

    image

           客户端回调提供了强大的功能。它能让你构建平滑的动态页面,不过要记住,这依赖 XMLHttpRequest 对象,它限制用户只能使用现代浏览器,有些浏览器支持 JavaScript 但不支持客户端回调。可以使用 Request.Browser.SupportsCallback 属性检查浏览器是否支持回调

  • 相关阅读:
    HDU 5744
    HDU 5815
    POJ 1269
    HDU 5742
    HDU 4609
    fzu 1150 Farmer Bill's Problem
    fzu 1002 HangOver
    fzu 1001 Duplicate Pair
    fzu 1150 Farmer Bill's Problem
    fzu 1182 Argus 优先队列
  • 原文地址:https://www.cnblogs.com/SkySoot/p/2827794.html
Copyright © 2011-2022 走看看