• 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 属性检查浏览器是否支持回调

  • 相关阅读:
    判断文件是否正在使用
    批量复制文件
    PAT 甲级 1116 Come on! Let's C (20 分)
    PAT 甲级 1116 Come on! Let's C (20 分)
    1123 Is It a Complete AVL Tree (30 分)
    1123 Is It a Complete AVL Tree (30 分)
    C++ sort()和is_sorted()的升序降序和自定义排序
    C++ sort()和is_sorted()的升序降序和自定义排序
    PAT 甲级 1103 Integer Factorization (30 分)
    PAT 甲级 1103 Integer Factorization (30 分)
  • 原文地址:https://www.cnblogs.com/SkySoot/p/2827794.html
Copyright © 2020-2023  润新知